home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / message.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-04  |  141.0 KB  |  2,769 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     message.c
  4.  
  5.     This module handles message windows.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <Script.h>
  14. #include <Aliases.h>
  15. #include <Icons.h>
  16. #include <Drag.h>
  17.  
  18. #include "glob.h"
  19. #include "message.h"
  20. #include "dialog.h"
  21. #include "header.h"
  22. #include "menus.h"
  23. #include "print.h"
  24. #include "strutil.h"
  25. #include "wind.h"
  26. #include "send.h"
  27. #include "newswatcher.h"
  28. #include "url.h"
  29. #include "tescroll.h"
  30. #include "drawutil.h"
  31. #include "memutil.h"
  32. #include "windutil.h"
  33. #include "teutil.h"
  34. #include "article.h"
  35. #include "fileutil.h"
  36. #include "sfutil.h"
  37. #include "prefs.h"
  38. #include "resutil.h"
  39. #include "iconutil.h"
  40. #include "listutil.h"
  41. #include "dragutil.h"
  42. #include "group.h"
  43. #include "status.h"
  44. #include "key.h"
  45. #include "ic.h"
  46.  
  47.  
  48.  
  49. #define kTooManyGroupsDlg            145
  50. #define kMessageFileExistsAlert        148
  51. #define kAskSendOrSaveAlert            134
  52.  
  53. #define kSendButtonID        500            /* Rsrc id of Send button control */
  54. #define kTabCheckboxID        501            /* Rsrc id of tab stops checkbox control */
  55. #define kWrapCheckboxID        502            /* Rsrc id of wrap checkbox control */
  56.  
  57. #define kIconPanelHeight    40            /* Height of icon panel */
  58.  
  59. #define kSendButtonTop        9            /* top coord of send button */
  60.  
  61. #define kNewsIconID            200            /* Resource id of news icon family */
  62. #define kMailIconID            201            /* Resource id of mail icon family */
  63. #define kSelfIconID            202            /* Resource id of self icon family */
  64.  
  65. #define kIconV                3            /* top coord of icons */
  66. #define kNewsIconH            30            /* left coord of news icon */
  67. #define kMailIconH            100            /* left coord of mail icon */
  68. #define kSelfIconH            175            /* left coord of self icon */
  69. #define kCheckMarkV            25            /* bot coord of check mark */
  70. #define kCheckMarkDeltaH    16            /* offset from left coord of check mark to left
  71.                                            coord of icon */
  72.                                            
  73. #define kOptionsPanelLeftMargin        10    /* left margin for options panel controls */
  74.  
  75. #define kMaxFields             15            /* max number of TE fields in window */
  76.  
  77. #define kMinWindowWidth        320            /* minimum window width */
  78.  
  79. #define kQuoteStringResID    128            /* 'TEXT' resource ids for saved message files */
  80. #define kNewsgroupsResID    129
  81. #define kToResID            130
  82. #define kSubjectResID        131
  83. #define kCcResID            132
  84. #define kBccResID            133
  85. #define kReplytoResID        134
  86. #define kFollowuptoResID    135
  87. #define kKeywordsResID        136
  88. #define kDistributionResID    137
  89. #define kExtraNewsResID        138
  90. #define kExtraMailResID        139
  91. #define kSignatureResID        140
  92. #define kReferencesResID    141
  93. #define kFromResID            142
  94.  
  95.  
  96.  
  97. #pragma options align=mac68k
  98. typedef struct TMiscMessageWindowInfo {
  99.     Boolean newsIcon;            /* true if news icon checked */
  100.     Boolean mailIcon;            /* true if mail icon checked */
  101.     Boolean selfIcon;            /* true if self icon checked */
  102.     Boolean tabEnabled;            /* true if body tabs currently enabled for this window */
  103.     Boolean wrapOnSend;            /* true to wrap message before sending it */
  104.     short tabStops;                /* current tab stops for this window */
  105. } TMiscMessageWindowInfo;
  106. #pragma options align=reset
  107.  
  108.  
  109.  
  110. static WindowPtr gDragDestWindow;        /* pointer to drag destination window */
  111. static TEHandle gDestField;                /* current drag destination field */
  112. static TEHandle gFinalDestField;        /* final drag destination field */
  113. static short gDestFieldOffset;            /* current offset into drag destination field */
  114. static short gFinalDestFieldOffset;        /* final offset into final drag destination field */
  115. static Handle gDragData;                /* handle to drag data */
  116. static Boolean gDragTextCopy;            /* true if text drag is copy, false if move */
  117. static Boolean gDragInsertTextQuoted;    /* true to insert dragged text quoted */
  118.  
  119. static TEClickLoopUPP gAutoScrollUPP;
  120. static DragTrackingHandlerUPP gHandleTrackingUPP;
  121. static DragReceiveHandlerUPP gHandleReceiveUPP;
  122. static ControlActionUPP gScrollActionUPP;
  123.  
  124.  
  125.  
  126. /*----------------------------------------------------------------------------
  127.     TooManyGroupsDialog 
  128.     
  129.     Present the "too many groups" dialog.
  130.             
  131.     Entry:    n = number of groups.
  132.             
  133.     Exit:    function result = error code.
  134. ----------------------------------------------------------------------------*/
  135.  
  136. static OSErr TooManyGroupsDialog (short n)
  137. {
  138.     OSErr err = noErr;
  139.     DialogPtr dlg = nil;
  140.     Str255 str;
  141.     short item;
  142.     
  143.     err = MyGetNewDialog(kTooManyGroupsDlg, cancel, cancel, &dlg);
  144.     if (err != noErr) return err;
  145.     NumToString(n, str);
  146.     ParamText(str, "\p", "\p", "\p");
  147.     SysBeep(0);
  148.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  149.     err = DoClose(dlg);
  150.     if (err != noErr) return err;
  151.     if (item == cancel) return userCanceledErr;
  152.     return noErr;
  153. }
  154.  
  155.  
  156.  
  157. /*----------------------------------------------------------------------------
  158.     GetViewRect 
  159.     
  160.     Compute the "view" rectangle of a message window
  161.             
  162.     Entry:    wind = pointer to message window.
  163.             
  164.     Exit:    *viewRect = text rectangle.
  165.     
  166.     The "view" rectangle is the content area of the window minus the
  167.     panel area and the scroll bar areas.
  168. ----------------------------------------------------------------------------*/
  169.  
  170. static void GetViewRect (WindowPtr wind, Rect *viewRect)
  171. {
  172.     TWindow **info;
  173.  
  174.     info = (TWindow**)GetWRefCon(wind);
  175.     *viewRect = wind->portRect;
  176.     viewRect->top += (**info).panelHeight;
  177.     viewRect->bottom -= 15;
  178.     viewRect->right -= 15;
  179. }
  180.  
  181.  
  182.  
  183. /*----------------------------------------------------------------------------
  184.     GetTextRect 
  185.     
  186.     Compute the "text" rectangle of a message window
  187.             
  188.     Entry:    wind = pointer to message window.
  189.             
  190.     Exit:    *textRect = text rectangle.
  191.     
  192.     The "text" rectangle is the view rectangle inset by kTextMargin on
  193.     all four sides. This is the area of the window where the text is 
  194.     displayed.
  195. ----------------------------------------------------------------------------*/
  196.  
  197. static void GetTextRect (WindowPtr wind, Rect *textRect)
  198. {
  199.     GetViewRect(wind, textRect);
  200.     InsetRect(textRect, kTextMargin, kTextMargin);
  201. }
  202.  
  203.  
  204.  
  205. /*----------------------------------------------------------------------------
  206.     InitCurField 
  207.     
  208.     Initialize the currently active field for a message window.
  209.             
  210.     Entry:    wind = pointer to message window.
  211. ----------------------------------------------------------------------------*/
  212.  
  213. static void InitCurField (WindowPtr wind)
  214. {
  215.     TWindow **info;
  216.     short i;
  217.     TMsgFieldInfo **fields, *f;
  218.     TEHandle edit;
  219.     
  220.     info = (TWindow**)GetWRefCon(wind);
  221.     fields = (**info).fields;
  222.     for (i = 0, f = *fields; ; i++, f++) {
  223.         edit = f->edit;
  224.         if ((**edit).teLength == 0) {
  225.             (**info).curField = i;
  226.             break;
  227.         }
  228.         if (edit == (**info).theTE) {
  229.             TESetSelect(0, 0, edit);
  230.             (**info).curField = i;
  231.             break;
  232.         }
  233.     }
  234.     if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitSet, edit);
  235. }
  236.  
  237.  
  238.  
  239. /*----------------------------------------------------------------------------
  240.     FixHeight 
  241.     
  242.     Round down window height to an exact multiple of lines.
  243.             
  244.     Entry:    wind = pointer to message window.
  245.             *height = window height.
  246.             
  247.     Exit:    *height = adjusted window height
  248. ----------------------------------------------------------------------------*/
  249.  
  250. static void FixHeight (WindowPtr wind, short *height)
  251. {
  252.     TWindow **info;
  253.     short panelHeight, lineHeight, adjust;
  254.  
  255.     info = (TWindow**)GetWRefCon(wind);
  256.     panelHeight = (**info).panelHeight;
  257.     lineHeight = GetFontLineHeight(wind);
  258.     adjust = panelHeight + 15 + 2*kTextMargin;
  259.     *height = (*height - adjust) / lineHeight * lineHeight + adjust;
  260. }
  261.  
  262.  
  263.  
  264. /*----------------------------------------------------------------------------
  265.     MinHeight 
  266.     
  267.     Compute the minimum height of a message window.
  268.             
  269.     Entry:    wind = pointer to message window.
  270. ----------------------------------------------------------------------------*/
  271.  
  272. static short MinHeight (WindowPtr wind)
  273. {
  274.     TWindow **info;
  275.     short lineHeight, height, extra;
  276.     
  277.     info = (TWindow**)GetWRefCon(wind);
  278.     lineHeight = GetFontLineHeight(wind);
  279.     extra = lineHeight + 15 + 2*kTextMargin;
  280.     if (extra < 65) extra = 65 + lineHeight;
  281.     height = (**info).panelHeight + (**info).optionsPanelHeight + extra;
  282.     FixHeight(wind, &height);
  283.     return height;
  284. }
  285.  
  286.  
  287.  
  288. /*----------------------------------------------------------------------------
  289.     MinWidth 
  290.     
  291.     Compute the minimum width of a message window.
  292.             
  293.     Entry:    wind = pointer to message window.
  294. ----------------------------------------------------------------------------*/
  295.  
  296. static short MinWidth (WindowPtr wind)
  297. {
  298.     TWindow **info;
  299.     TEHandle field;
  300.     short tabFieldRight, quoteStringFieldRight;
  301.     short minWidth;
  302.     
  303.     info = (TWindow**)GetWRefCon(wind);
  304.     field = (**info).tabField;
  305.     tabFieldRight = (**field).viewRect.right;
  306.     field = (**info).quoteStringField;
  307.     quoteStringFieldRight = (**field).viewRect.right;
  308.     minWidth = 10 + (tabFieldRight < quoteStringFieldRight ? quoteStringFieldRight : tabFieldRight);
  309.     if (minWidth < kMinWindowWidth) minWidth = kMinWindowWidth;
  310.     return minWidth;
  311. }
  312.  
  313.  
  314.  
  315. /*----------------------------------------------------------------------------
  316.     ChangeSubject 
  317.     
  318.     Change the subject in a message window.
  319.             
  320.     Entry:    wind = pointer to message window.
  321. ----------------------------------------------------------------------------*/
  322.  
  323. static void ChangeSubject (WindowPtr wind)
  324. {
  325.     TWindow **info;
  326.     TEHandle edit;
  327.     Str255 subject, title;
  328.     
  329.     info = (TWindow**)GetWRefCon(wind);
  330.     if ((**info).alias != nil) return;
  331.     edit = (**info).subjectField;
  332.     GetDialogItemText((**edit).hText, subject);
  333.     GetWTitle(wind, title);
  334.     if (*subject == 0) GetPString(kStrNoSubject, subject);
  335.     if (!EqualString(subject, title, true, true)) {
  336.         SetWTitle(wind, subject);
  337.     }
  338. }
  339.  
  340.  
  341.  
  342. /*----------------------------------------------------------------------------
  343.     ComputeFieldViewRect 
  344.     
  345.     Compute the view rectangle for a TextEdit field given the destination 
  346.     rectangle.
  347.             
  348.     Entry:    wind = pointer to message window.
  349.             *destRect = destination rectangle.
  350.             
  351.     Exit:    *viewRect = view rectangle.
  352.     
  353.     The TextEdit view rectangle is set to the destination rectangle
  354.     clipped to the window text rectangle. If the result is empty, the view
  355.     rectangle is set to an off-screen rectangle.
  356. ----------------------------------------------------------------------------*/
  357.  
  358. static void ComputeFieldViewRect (WindowPtr wind, Rect *destRect, Rect *viewRect)
  359. {
  360.     Rect textRect;
  361.     
  362.     GetTextRect(wind, &textRect);
  363.     *viewRect = *destRect;
  364.     if (viewRect->top < textRect.top) viewRect->top = textRect.top;
  365.     if (viewRect->bottom > textRect.bottom) viewRect->bottom = textRect.bottom;
  366.     if (viewRect->top >= viewRect->bottom) SetRect(viewRect, 0, 0x7700, 500, 0x77ff);
  367. }
  368.  
  369.  
  370.  
  371. /*----------------------------------------------------------------------------
  372.     AdjustLastFieldViewRect 
  373.     
  374.     Adjust the view rectangle for the last TextEdit field in a window.
  375.             
  376.     Entry:    wind = pointer to message window.
  377.     
  378.     If the view rectangle for the last TextEdit field does not extend to the
  379.     bottom of the window's text rectangle, it is extended to reach the bottom.
  380. ----------------------------------------------------------------------------*/
  381.  
  382. static void AdjustLastFieldViewRect (WindowPtr wind)
  383. {
  384.     TWindow **info;
  385.     TMsgFieldInfo **fields;
  386.     short numFields;
  387.     TEHandle lastEdit;
  388.     Rect textRect;
  389.     
  390.     info = (TWindow**)GetWRefCon(wind);
  391.     fields = (**info).fields;
  392.     numFields = (**info).numFields;
  393.     lastEdit = (*fields)[numFields - 1].edit;
  394.     GetTextRect(wind, &textRect);
  395.     if ((**lastEdit).viewRect.bottom < textRect.bottom) 
  396.         (**lastEdit).viewRect.bottom = textRect.bottom;
  397. }
  398.  
  399.  
  400.  
  401. /*----------------------------------------------------------------------------
  402.     AdjustScrollMax 
  403.     
  404.     Adjust the scroll bar maximum value.
  405.             
  406.     Entry:    wind = pointer to message window.
  407. ----------------------------------------------------------------------------*/
  408.  
  409. static void AdjustScrollMax (WindowPtr wind)
  410. {
  411.     TWindow **info;
  412.     TMsgFieldInfo **fields;
  413.     short numFields, max, windHeight, firstScrollingField; 
  414.     short top, bottom, height, lineHeight;
  415.     Rect textRect;
  416.     
  417.     info = (TWindow**)GetWRefCon(wind);
  418.     fields = (**info).fields;
  419.     numFields = (**info).numFields;
  420.     firstScrollingField = (**info).firstScrollingField;
  421.     top = (*fields)[firstScrollingField].top;
  422.     bottom = (*fields)[numFields-1].bottom;
  423.     GetTextRect(wind, &textRect);
  424.     windHeight = textRect.bottom - textRect.top;
  425.     if (bottom < textRect.bottom) bottom = textRect.bottom;
  426.     height = bottom - top;
  427.     max = height - windHeight;
  428.     if (max < 0) max = 0;
  429.     lineHeight = (**(**info).theTE).lineHeight;
  430.     SetControlMaximum((**info).vScroll, max/lineHeight);
  431. }
  432.  
  433.  
  434.  
  435. /*----------------------------------------------------------------------------
  436.     Scroll 
  437.     
  438.     Scroll a message window.
  439.             
  440.     Entry:    wind = pointer to message window.
  441.             dv = number of lines to scroll.
  442.             
  443.     If the vertical scroll bar's refCon is non-zero, the scroll bar max value is
  444.     adjusted after the scrolling operation. This is what you normally want.
  445.     The only exception is when scrolling in a TrackControl action procedure,
  446.     when you want to set the refCon to zero.
  447. ----------------------------------------------------------------------------*/
  448.  
  449. static void Scroll (WindowPtr wind, short dv)
  450. {
  451.     TWindow **info;
  452.     TMsgFieldInfo **fields, *f;
  453.     Rect r, destRect, viewRect, inval;
  454.     static RgnHandle rgn = nil;
  455.     TEHandle edit;
  456.     short numFields, i, firstScrollingField, lineHeight;
  457.  
  458.     info = (TWindow**)GetWRefCon(wind);
  459.     lineHeight = (**(**info).theTE).lineHeight;
  460.     dv = dv * lineHeight;
  461.     fields = (**info).fields;
  462.     GetViewRect(wind, &r);
  463.     InsetRect(&r, 0, kTextMargin);
  464.     if (rgn == nil) rgn = NewRgn();
  465.     ScrollRect(&r, 0, dv, rgn);
  466.     InvalRgn(rgn);
  467.     if (!((WindowPeek)wind)->hilited) {
  468.         inval = r;
  469.         if (dv > 0) {
  470.             if (inval.top + dv + lineHeight < inval.bottom) 
  471.                 inval.bottom = inval.top + dv + lineHeight;
  472.         } else {
  473.             if (inval.bottom + dv - lineHeight > inval.top) 
  474.                 inval.top = inval.bottom + dv - lineHeight;
  475.         }
  476.         InvalRect(&inval);
  477.         inval = r;
  478.         if (dv > 0) {
  479.             inval.top = inval.bottom - lineHeight;
  480.         } else {
  481.             inval.bottom = inval.top + lineHeight;
  482.         }
  483.         InvalRect(&inval);
  484.     }
  485.         
  486.     SetOrigin(0, 0);
  487.     numFields = (**info).numFields;
  488.     firstScrollingField = (**info).firstScrollingField;
  489.     for (i = firstScrollingField; i < numFields; i++) {
  490.         f = &(*fields)[i];
  491.         edit = f->edit;
  492.         f->top += dv;
  493.         f->bottom += dv;
  494.         destRect = (**edit).destRect;
  495.         OffsetRect(&destRect, 0, dv);
  496.         ComputeFieldViewRect(wind, &destRect, &viewRect);
  497.         (**edit).destRect = destRect;
  498.         (**edit).viewRect = viewRect;
  499.     }
  500.     AdjustLastFieldViewRect(wind);
  501.     if (GetControlReference((**info).vScroll) != 0) AdjustScrollMax(wind);
  502.     HandleUpdate(wind);
  503. }
  504.  
  505.  
  506.  
  507. /*----------------------------------------------------------------------------
  508.     ScrollRangeIntoView 
  509.     
  510.     Scroll a range of characters into view, if necessary.
  511.             
  512.     Entry:    wind = pointer to message window.
  513.             start = starting offset of text range in current field.
  514.             end = ending offet of text range in current field.
  515. ----------------------------------------------------------------------------*/
  516.  
  517. static void ScrollRangeIntoView (WindowPtr wind, short start, short end)
  518. {
  519.     TWindow **info;
  520.     TMsgFieldInfo **fields;
  521.     short curField, lineStart, lineEnd, top, lineHeight, vStart, vEnd;
  522.     short oldScrollVal, max, dv;
  523.     ControlHandle vScroll;
  524.     TEHandle edit;
  525.     Rect textRect;
  526.     Boolean tooBig;
  527.     short selStart, selEnd, savedClikStuff;
  528.  
  529.     AdjustScrollMax(wind);
  530.     info = (TWindow**)GetWRefCon(wind);
  531.     fields = (**info).fields;
  532.     curField = (**info).curField;
  533.     if (curField < (**info).firstScrollingField) return;
  534.     vScroll = (**info).vScroll;
  535.     edit = (*fields)[curField].edit;
  536.     
  537.     savedClikStuff = (**edit).clikStuff;
  538.     selStart = (**edit).selStart;
  539.     selEnd = (**edit).selEnd;
  540.     if (start == end && selStart < selEnd) {
  541.         if (start == selEnd) {
  542.             (**edit).clikStuff = 0;
  543.         } else if (end == selStart) {
  544.             (**edit).clikStuff = 0xffff;
  545.         }
  546.         lineStart = TEScrollGetTELineNumber(start, edit);
  547.         lineEnd = TEScrollGetTELineNumber(end, edit);
  548.     } else if (start < end) {
  549.         (**edit).clikStuff = 0xffff;
  550.         lineStart = TEScrollGetTELineNumber(start, edit);
  551.         (**edit).clikStuff = 0;
  552.         lineEnd = TEScrollGetTELineNumber(end, edit);
  553.     } else {
  554.         lineStart = TEScrollGetTELineNumber(start, edit);
  555.         lineEnd = TEScrollGetTELineNumber(end, edit);
  556.     }
  557.     (**edit).clikStuff = savedClikStuff;
  558.     
  559.     top = (**edit).destRect.top;
  560.     lineHeight = (**edit).lineHeight;
  561.     vStart = top + lineStart*lineHeight;
  562.     vEnd = top + (lineEnd+1)*lineHeight;
  563.     GetTextRect(wind, &textRect);
  564.     tooBig = (vEnd - vStart) > (textRect.bottom - textRect.top);
  565.     if (vEnd > textRect.bottom) {
  566.         if (vStart < textRect.bottom - lineHeight) return;
  567.         if (tooBig) {
  568.             dv = textRect.top - vStart;
  569.         } else {
  570.             dv = textRect.bottom - vEnd;
  571.         }
  572.     } else if (vStart < textRect.top) {
  573.         if (vEnd > textRect.top + lineHeight) return;
  574.         if (tooBig) {
  575.             dv = textRect.bottom - vEnd;
  576.         } else {
  577.             dv = textRect.top - vStart;
  578.         }
  579.     } else {
  580.         AdjustScrollMax(wind);
  581.         return;
  582.     }
  583.     dv = dv/lineHeight;
  584.     oldScrollVal = GetControlValue(vScroll);
  585.     max = GetControlMaximum(vScroll);
  586.     if (oldScrollVal - dv > max) dv = oldScrollVal - max;
  587.     Scroll(wind, dv);
  588.     SetControlValue(vScroll, oldScrollVal - dv);
  589. }
  590.  
  591.  
  592.  
  593. /*----------------------------------------------------------------------------
  594.     ScrollSelectionIntoView 
  595.     
  596.     Scroll the current selection into view, if necessary.
  597.             
  598.     Entry:    wind = pointer to message window.
  599. ----------------------------------------------------------------------------*/
  600.  
  601. static void ScrollSelectionIntoView (WindowPtr wind)
  602. {
  603.     TWindow **info;
  604.     TMsgFieldInfo **fields;
  605.     short curField;
  606.     TEHandle edit;
  607.  
  608.     info = (TWindow**)GetWRefCon(wind);
  609.     fields = (**info).fields;
  610.     curField = (**info).curField;
  611.     if (curField < (**info).firstScrollingField) return;
  612.     edit = (*fields)[curField].edit;
  613.     ScrollRangeIntoView (wind, (**edit).selStart, (**edit).selEnd);
  614. }
  615.  
  616.  
  617.  
  618. /*----------------------------------------------------------------------------
  619.     ScrollToMiddle 
  620.     
  621.     Scroll a given point in a field to the middle of the window, if necessary.
  622.             
  623.     Entry:    wind = pointer to message window.
  624.             offset = offset of character in current field to scroll to middle.
  625. ----------------------------------------------------------------------------*/
  626.  
  627. static void ScrollToMiddle (WindowPtr wind, short offset)
  628. {
  629.     TWindow **info;
  630.     TMsgFieldInfo **fields;
  631.     short curField;
  632.     ControlHandle vScroll;
  633.     TEHandle edit;
  634.     short lineStart, top, lineHeight, vStart;
  635.     short oldScrollVal, max, dv, textHeight;
  636.     Rect textRect;
  637.     short savedClikStuff;
  638.  
  639.     AdjustScrollMax(wind);
  640.     info = (TWindow**)GetWRefCon(wind);
  641.     curField = (**info).curField;
  642.     if (curField < (**info).firstScrollingField) return;
  643.     fields = (**info).fields;
  644.     vScroll = (**info).vScroll;
  645.     edit = (*fields)[curField].edit;
  646.  
  647.     savedClikStuff = (**edit).clikStuff;
  648.     (**edit).clikStuff = 0xffff;
  649.     lineStart = TEScrollGetTELineNumber(offset, edit);
  650.     (**edit).clikStuff = savedClikStuff;
  651.     top = (**edit).destRect.top;
  652.     lineHeight = (**edit).lineHeight;
  653.     vStart = top + lineStart*lineHeight;
  654.     GetTextRect(wind, &textRect);
  655.     if (vStart >= textRect.top && vStart <= textRect.bottom - lineHeight) return;
  656.     textHeight = (textRect.bottom - textRect.top) / lineHeight;
  657.     dv = (textRect.top - vStart)/lineHeight + (textHeight >> 1);
  658.     oldScrollVal = GetControlValue(vScroll);
  659.     max = GetControlMaximum(vScroll);
  660.     if (oldScrollVal - dv > max) dv = oldScrollVal - max;
  661.     Scroll(wind, dv);
  662.     SetControlValue(vScroll, oldScrollVal - dv); 
  663. }
  664.  
  665.  
  666.  
  667. /*----------------------------------------------------------------------------
  668.     GetPageHeight 
  669.     
  670.     Get the page height.
  671.             
  672.     Entry:    wind = pointer to message window.
  673.     
  674.     Exit:    function result = page height.
  675. ----------------------------------------------------------------------------*/
  676.  
  677. static short GetPageHeight (WindowPtr wind)
  678. {
  679.     TWindow **info;
  680.     TEHandle theTE;
  681.     short lineHeight;
  682.     Rect r;
  683.  
  684.     info = (TWindow**)GetWRefCon(wind);
  685.     theTE = (**info).theTE;
  686.     lineHeight = (**theTE).lineHeight;
  687.     GetTextRect(wind, &r);
  688.     return (r.bottom - r.top) / lineHeight - 1;
  689. }
  690.  
  691.  
  692.  
  693. /*----------------------------------------------------------------------------
  694.     ScrollAction 
  695.     
  696.     Scroll bar action procedure.
  697.             
  698.     Entry:    vScroll = handle to vertical scroll bar control
  699.             part = part code.
  700. ----------------------------------------------------------------------------*/
  701.  
  702. static pascal void ScrollAction (ControlHandle vScroll, short part)
  703. {
  704.     WindowPtr wind;
  705.     short val, max, page, dv;
  706.  
  707.     wind = (**vScroll).contrlOwner;
  708.     val = (**vScroll).contrlValue;
  709.     max = (**vScroll).contrlMax;
  710.     page = GetPageHeight(wind);
  711.     dv = 0;
  712.     switch (part) {
  713.         case inUpButton:
  714.             dv = val > 0 ? 1 : 0;
  715.             break;
  716.         case inDownButton:
  717.             dv = val < max ? -1 : 0;
  718.             break;
  719.         case inPageUp:
  720.             dv = val > page ? page : val;
  721.             break;
  722.         case inPageDown:
  723.             dv = val < max - page ? -page : val - max;
  724.             break;
  725.         case kScrollToHome:
  726.             dv = val;
  727.             break;
  728.         case kScrollToEnd:
  729.             dv = val - max;
  730.             break;
  731.     }
  732.     if (dv != 0) {
  733.         SetControlValue(vScroll, val - dv);
  734.         Scroll(wind, dv);
  735.     }
  736. }
  737.  
  738.  
  739.  
  740. /*----------------------------------------------------------------------------
  741.     AutoScroll 
  742.     
  743.     Handle message window autoscrolling.
  744.             
  745.     Exit:    function result = true
  746. ----------------------------------------------------------------------------*/
  747.  
  748. static pascal Boolean AutoScroll (void)
  749. {
  750.     WindowPtr wind;
  751.     TWindow **info;
  752.     ControlHandle vScroll;
  753.     TMsgFieldInfo **fields;
  754.     short curField;
  755.     TEHandle edit;
  756.     Rect r;
  757.     Point where;
  758.     short val, max;
  759.  
  760.     wind = MyFrontWindow();
  761.     if (wind == nil) return true;
  762.     info = (TWindow**)GetWRefCon(wind);
  763.     vScroll = (**info).vScroll;
  764.     fields = (**info).fields;
  765.     curField = (**info).curField;
  766.     edit = (*fields)[curField].edit;
  767.     val = GetControlValue(vScroll);
  768.     max = GetControlMaximum(vScroll);
  769.     GetTextRect(wind, &r);
  770.     GetMouse(&where);
  771.     ClipRect(&wind->portRect);
  772.     if (where.v < r.top && val > 0) {
  773.         Scroll(wind, 1);
  774.         SetControlValue(vScroll, val-1);
  775.     } else if (where.v > r.bottom && val < max) {
  776.         Scroll(wind, -1);
  777.         SetControlValue(vScroll, val+1);
  778.     }
  779.     r = (**edit).viewRect;
  780.     ClipRect(&r);
  781.     return true;
  782. }
  783.  
  784.  
  785.  
  786. /*----------------------------------------------------------------------------
  787.     AddFieldInfo 
  788.     
  789.     Add one new field to array of field info.
  790.             
  791.     Entry:    wind = pointer to message window.
  792.             edit = handle to TextEdit record.
  793.             sepLine = true if field preceeded by gray separator line.
  794.             labelKind = kind of label for this field.
  795.             labelIndex = index in STR# 128 of field label, or 0 if none.
  796. ----------------------------------------------------------------------------*/
  797.  
  798. static void AddFieldInfo (WindowPtr wind, TEHandle edit, Boolean sepLine, 
  799.     TMsgFieldLabelKind labelKind, short labelIndex)
  800. {
  801.     TWindow **info;
  802.     short index;
  803.     TMsgFieldInfo **fields;
  804.     TMsgFieldInfo f;
  805.     short nLines;
  806.     short lineHeight, height, h;
  807.     static short v;
  808.     Rect textRect, destRect, viewRect;
  809.     Str255 label;
  810.         
  811.     if (labelIndex > 0) GetPString(labelIndex, label);
  812.     info = (TWindow**)GetWRefCon(wind);
  813.     index = (**info).numFields;
  814.     fields = (**info).fields;
  815.     f.edit = edit;
  816.     f.sepLine = sepLine;
  817.     f.labelKind = labelKind;
  818.     if (labelIndex > 0) BlockMoveData(label, f.label, *label+1);
  819.         
  820.     if (index == (**info).firstScrollingField) v = (**info).panelHeight + kTextMargin;
  821.     f.top = v;
  822.     lineHeight = (**edit).lineHeight;
  823.     if (sepLine) v += lineHeight;
  824.     switch (labelKind) {
  825.         case kNoMsgFieldLabel:
  826.             h = kTextMargin;
  827.             break;
  828.         case kMsgFieldLabelLeft:
  829.             h = (**info).labelRight;
  830.             break;
  831.         case kMsgFieldLabelTop:
  832.             h = kTextMargin;
  833.             v += lineHeight;
  834.             break;
  835.     }
  836.     GetTextRect(wind, &textRect);
  837.     SetRect(&destRect, h, v, textRect.right, v + 1000);
  838.     (**edit).viewRect = (**edit).destRect = destRect;
  839.     TECalText(edit);
  840.     f.nLines = nLines = TEScrollNumTELines(edit);
  841.     height = nLines * lineHeight;
  842.     v += height;
  843.     destRect.bottom = f.bottom = v;
  844.     ComputeFieldViewRect(wind, &destRect, &viewRect);
  845.     (**edit).viewRect = viewRect;
  846.     (**edit).destRect = destRect;
  847.     (*fields)[index] = f;
  848.     (**info).numFields++;
  849. }
  850.  
  851.  
  852.  
  853. /*----------------------------------------------------------------------------
  854.     RebuildFields 
  855.     
  856.     Rebuild the field info for a message window.
  857.             
  858.     Entry:    wind = pointer to message window.
  859. ----------------------------------------------------------------------------*/
  860.  
  861. static void RebuildFields (WindowPtr wind)
  862. {
  863.     TWindow **info;
  864.     TMsgFieldInfo **fields;
  865.     TEHandle signatureField;
  866.     
  867.     info = (TWindow**)GetWRefCon(wind);
  868.     fields = (**info).fields;
  869.     
  870.     if ((**info).showDetails) {
  871.         (*fields)[0].edit = (**info).tabField;
  872.         (*fields)[1].edit = (**info).quoteStringField;
  873.         (**info).firstScrollingField = (**info).numFields = 2;
  874.     } else {
  875.         (**info).firstScrollingField = (**info).numFields = 0;
  876.     }
  877.     if ((**info).newsIcon) { 
  878.         AddFieldInfo(wind, (**info).newsgroupsField, false, kMsgFieldLabelLeft, 
  879.             kStrNewsgroups);
  880.     }
  881.     if ((**info).mailIcon) {
  882.         AddFieldInfo(wind, (**info).toField, false, kMsgFieldLabelLeft,
  883.             kStrTo);
  884.     }
  885.     AddFieldInfo(wind, (**info).subjectField, false, kMsgFieldLabelLeft, 
  886.         kStrSubject);
  887.     if ((**info).showDetails) {
  888.         if ((**info).mailIcon) {
  889.             AddFieldInfo(wind, (**info).ccField, false, kMsgFieldLabelLeft, 
  890.                 kStrCc);
  891.             AddFieldInfo(wind, (**info).bccField, false, kMsgFieldLabelLeft,
  892.                 kStrBcc);
  893.         }
  894.         AddFieldInfo(wind, (**info).replytoField, false, kMsgFieldLabelLeft, 
  895.             kStrReplyto);
  896.         if ((**info).newsIcon) {
  897.             AddFieldInfo(wind, (**info).followuptoField, false, kMsgFieldLabelLeft,
  898.                 kStrFollowupto);
  899.         }
  900.         AddFieldInfo(wind, (**info).keywordsField, false, kMsgFieldLabelLeft, 
  901.             kStrKeywords);
  902.         if ((**info).newsIcon) {
  903.             AddFieldInfo(wind, (**info).distributionField, false, kMsgFieldLabelLeft,
  904.                 kStrDistribution);
  905.             AddFieldInfo(wind, (**info).extraNewsField, true, kMsgFieldLabelTop,
  906.                 kStrExtraNewsHeaderLines);
  907.         }
  908.         if ((**info).mailIcon) {
  909.             AddFieldInfo(wind, (**info).extraMailField, true, kMsgFieldLabelTop,
  910.                 kStrExtraMailHeaderLines);
  911.         }
  912.     }
  913.     
  914.     AddFieldInfo(wind, (**info).theTE, true, kNoMsgFieldLabel, 0);
  915.     
  916.     if ((**info).showDetails) {
  917.         signatureField = (**info).signatureField;
  918.         if ((**signatureField).teLength == 0) {
  919.             AddFieldInfo(wind, signatureField, true, kMsgFieldLabelTop, 
  920.                 kStrSignature);
  921.         } else {
  922.             AddFieldInfo(wind, signatureField, true, kNoMsgFieldLabel, 0);
  923.         }
  924.     }
  925.     
  926.     AdjustLastFieldViewRect(wind);
  927.     AdjustScrollMax(wind);
  928.     SetControlValue((**info).vScroll, 0);
  929. }
  930.  
  931.  
  932.  
  933. /*----------------------------------------------------------------------------
  934.     ChangeDisplayedFields 
  935.     
  936.     Change the currently displayed fields in a message window.
  937.             
  938.     Entry:    wind = pointer to message window.
  939. ----------------------------------------------------------------------------*/
  940.  
  941. static void ChangeDisplayedFields (WindowPtr wind)
  942. {
  943.     TWindow **info;
  944.     TMsgFieldInfo **fields, *f;
  945.     TEHandle oldEdit, newEdit;
  946.     short oldField, newField, numFields;
  947.     Rect viewRect;
  948.     
  949.     info = (TWindow**)GetWRefCon(wind);
  950.     fields = (**info).fields;
  951.     oldField = (**info).curField;
  952.     oldEdit = (*fields)[oldField].edit;
  953.     RebuildFields(wind);
  954.     numFields = (**info).numFields;
  955.     
  956.     for (newField = 0, f = *fields; newField < numFields; newField++, f++) {
  957.         newEdit = f->edit;
  958.         if (oldEdit == newEdit) break;
  959.         if (newEdit == (**info).newsgroupsField ||
  960.             newEdit == (**info).toField ||
  961.             newEdit == (**info).subjectField)
  962.         {
  963.             if ((**newEdit).teLength == 0) break;
  964.         }
  965.     }
  966.     if (newField >= numFields) {
  967.         InitCurField(wind);
  968.         newField = (**info).curField;
  969.         newEdit = (*fields)[newField].edit;
  970.     }
  971.     (**info).curField = newField;
  972.     if (newEdit != oldEdit) {
  973.         if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitClear, oldEdit);
  974.         TESetSelect(0, 0, oldEdit);
  975.         TEDeactivate(oldEdit);
  976.         MyTEActivate(newEdit);
  977.         if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitSet, newEdit);
  978.     }
  979.     ScrollSelectionIntoView(wind);
  980.     GetViewRect(wind, &viewRect);
  981.     InvalRect(&viewRect);
  982. }
  983.  
  984.  
  985.  
  986. /*----------------------------------------------------------------------------
  987.     UncheckNewsIcon 
  988.     
  989.     Uncheck the news icon in a message window.
  990.             
  991.     Entry:    wind = pointer to message window.
  992. ----------------------------------------------------------------------------*/
  993.  
  994. void UncheckNewsIcon (WindowPtr wind)
  995. {
  996.     TWindow **info;
  997.     Rect r;
  998.  
  999.     info = (TWindow**)GetWRefCon(wind);
  1000.     (**info).newsIcon = false;
  1001.     SetRect(&r, kNewsIconH - kCheckMarkDeltaH, kIconV, kNewsIconH - 1, kIconV + 32);
  1002.     InvalRect(&r);
  1003.     ChangeDisplayedFields(wind);
  1004.     HandleUpdate(wind);
  1005. }
  1006.  
  1007.  
  1008.  
  1009. /*----------------------------------------------------------------------------
  1010.     ResizeContents 
  1011.     
  1012.     Adjust a message window's contents after a window size change (grow
  1013.     or zoom).
  1014.             
  1015.     Entry:    wind = pointer to message window.
  1016. ----------------------------------------------------------------------------*/
  1017.  
  1018. static void ResizeContents (WindowPtr wind)
  1019. {
  1020.     TWindow **info;
  1021.     short width, height, panelHeight;
  1022.     ControlHandle vScroll, sendButton;
  1023.     Rect r;
  1024.  
  1025.     info = (TWindow**)GetWRefCon(wind);
  1026.     panelHeight = (**info).panelHeight;
  1027.     vScroll = (**info).vScroll;
  1028.     sendButton = (**info).sendButton;
  1029.     width = wind->portRect.right;
  1030.     height = wind->portRect.bottom;
  1031.     
  1032.     SetRect(&r, width-15, panelHeight-1, width+1, height-14);
  1033.     (**vScroll).contrlRect = r;
  1034.  
  1035.     SetRect(&r, width-70, kSendButtonTop, width-10, kSendButtonTop+20);
  1036.     (**sendButton).contrlRect = r;    
  1037.     
  1038.     RebuildFields(wind);
  1039.     InvalRect(&wind->portRect);
  1040.     ScrollSelectionIntoView(wind);
  1041. }
  1042.  
  1043.  
  1044.  
  1045. /*----------------------------------------------------------------------------
  1046.     ChangeCurField 
  1047.     
  1048.     Change the currently active field in a message window.
  1049.             
  1050.     Entry:    wind = pointer to message window.
  1051.             newCurField = index in field info array of new active field.
  1052. ----------------------------------------------------------------------------*/
  1053.  
  1054. static void ChangeCurField (WindowPtr wind, short newCurField)
  1055. {
  1056.     TWindow **info;
  1057.     TMsgFieldInfo **fields;
  1058.     short curField;
  1059.     TEHandle edit;
  1060.     Str255 tabStopsStr;
  1061.     long tabStops;
  1062.     
  1063.     info = (TWindow**)GetWRefCon(wind);
  1064.     fields = (**info).fields;
  1065.     curField = (**info).curField;
  1066.     edit = (*fields)[curField].edit;
  1067.     if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitClear, edit);
  1068.     TESetSelect(0, 0, edit);
  1069.     TEDeactivate(edit);
  1070.     if (edit == (**info).subjectField) ChangeSubject(wind);
  1071.     if (edit == (**info).tabField) {
  1072.         GetDialogItemText((**edit).hText, tabStopsStr);
  1073.         StringToNum(tabStopsStr, &tabStops);
  1074.         (**info).tabStops = tabStops;
  1075.     }
  1076.     edit = (*fields)[newCurField].edit;
  1077.     MyTEActivate(edit);
  1078.     if (gHaveTEOutlineHilite) TEFeatureFlag(teFOutlineHilite, TEBitSet, edit);
  1079.     (**info).curField = newCurField;
  1080. }
  1081.  
  1082.  
  1083.  
  1084. /*----------------------------------------------------------------------------
  1085.     GetFieldIndex 
  1086.     
  1087.     Get the index of a field in the fields array.
  1088.             
  1089.     Entry:    wind = pointer to message window.
  1090.             edit = handle to TextEdit field.
  1091.             
  1092.     Exit:    function result = index of field in fields array, or -1
  1093.             if not found.
  1094. ----------------------------------------------------------------------------*/
  1095.  
  1096. static short GetFieldIndex (WindowPtr wind, TEHandle edit)
  1097. {
  1098.     TWindow **info;
  1099.     TMsgFieldInfo **fields;
  1100.     short numFields, i;
  1101.     
  1102.     info = (TWindow**)GetWRefCon(wind);
  1103.     fields = (**info).fields;
  1104.     numFields = (**info).numFields;
  1105.     
  1106.     for (i = 0; i < numFields; i++)
  1107.         if ((*fields)[i].edit == edit) break;
  1108.     return i < numFields ? i : -1;
  1109. }
  1110.  
  1111.  
  1112.  
  1113. /*----------------------------------------------------------------------------
  1114.     AdjustFieldHeight 
  1115.     
  1116.     Adjust the height of a field after a field change.
  1117.             
  1118.     Entry:    wind = pointer to message window.
  1119.             edit = handle to TextEdit field to adjust.
  1120. ----------------------------------------------------------------------------*/
  1121.  
  1122. static void AdjustFieldHeight (WindowPtr wind, TEHandle edit)
  1123. {
  1124.     TWindow **info;
  1125.     TMsgFieldInfo **fields, *f, *g;
  1126.     short fieldIndex, i, nLines, lineHeight, numFields, v, height;
  1127.     Rect destRect, viewRect, r;
  1128.     short oldBottom, newBottom;
  1129.     char state;
  1130.     Boolean active;
  1131.     
  1132.     info = (TWindow**)GetWRefCon(wind);
  1133.     fields = (**info).fields;
  1134.     numFields = (**info).numFields;
  1135.     
  1136.     fieldIndex = GetFieldIndex(wind, edit);
  1137.     if (fieldIndex < (**info).firstScrollingField) return;
  1138.     active = ((WindowPeek)wind)->hilited;
  1139.     
  1140.     state = MyHGetState(fields);
  1141.     MyHLock(fields);
  1142.     f = &(*fields)[fieldIndex];
  1143.     nLines = TEScrollNumTELines(edit);
  1144.     oldBottom = f->bottom;
  1145.     if (nLines != f->nLines) {
  1146.         lineHeight = (**edit).lineHeight;
  1147.         f->nLines = nLines;
  1148.         v = f->top;
  1149.         for (i = fieldIndex, g = f; i < numFields; i++, g++) {
  1150.             edit = g->edit;
  1151.             g->top = v;
  1152.             if (g->sepLine) v += lineHeight;
  1153.             if (g->labelKind == kMsgFieldLabelTop) v += lineHeight;
  1154.             height = g->nLines * lineHeight;
  1155.             destRect = (**edit).destRect;
  1156.             destRect.top = v;
  1157.             destRect.bottom = v + height;
  1158.             ComputeFieldViewRect(wind, &destRect, &viewRect);
  1159.             v += height;
  1160.             g->bottom = v;
  1161.             (**edit).viewRect = viewRect;
  1162.             (**edit).destRect = destRect;
  1163.         }
  1164.         AdjustLastFieldViewRect(wind);
  1165.         newBottom = f->bottom;
  1166.         if (active) {
  1167.             GetViewRect(wind, &r);
  1168.             r.top = oldBottom < newBottom ? oldBottom : newBottom;
  1169.             InvalRect(&r);
  1170.             HandleUpdate(wind);
  1171.         }
  1172.         AdjustScrollMax(wind);
  1173.     }
  1174.     MyHSetState(fields, state);
  1175.     ScrollSelectionIntoView(wind);
  1176.     if (!active) {
  1177.         GetViewRect(wind, &viewRect);
  1178.         InvalRect(&viewRect);
  1179.         HandleUpdate(wind);
  1180.     }
  1181. }
  1182.  
  1183.  
  1184.  
  1185. /*----------------------------------------------------------------------------
  1186.     AdjustCurFieldHeight 
  1187.     
  1188.     Adjust the height of the current field after a field change.
  1189.             
  1190.     Entry:    wind = pointer to message window.
  1191. ----------------------------------------------------------------------------*/
  1192.  
  1193. static void AdjustCurFieldHeight (WindowPtr wind)
  1194. {
  1195.     TWindow **info;
  1196.     TMsgFieldInfo **fields;
  1197.     TEHandle edit;
  1198.     short curField;
  1199.     
  1200.     info = (TWindow**)GetWRefCon(wind);
  1201.     fields = (**info).fields;
  1202.     curField = (**info).curField;
  1203.     if (curField < (**info).firstScrollingField) return;
  1204.     edit = (*fields)[curField].edit;
  1205.     AdjustFieldHeight(wind, edit);
  1206. }
  1207.  
  1208.  
  1209.  
  1210. /*----------------------------------------------------------------------------
  1211.     InitializeGroupList 
  1212.     
  1213.     Initialize the group list for a new message window.
  1214.             
  1215.     Entry:    wind = pointer to active window, or nil if none.
  1216.             newsgroups = handle to "Newsgroups" textedit field.
  1217.             
  1218.     Exit:    function result = error code.
  1219. ----------------------------------------------------------------------------*/
  1220.  
  1221. static OSErr InitializeGroupList (WindowPtr wind, TEHandle newsgroups)
  1222. {
  1223.     TWindow **info, **parentInfo;
  1224.     ListHandle theList;
  1225.     TGroup **groupArray;
  1226.     Point theCell;
  1227.     short numSelected, cellDataLen, index;
  1228.     CStr255 groupsStr;
  1229.     TWindowKind kind;
  1230.     OSErr err = noErr;
  1231.  
  1232.     kind = GetWindowKind(wind);
  1233.     if (kind == kNotOurWind) return noErr;
  1234.     info = (TWindow**)GetWRefCon(wind);
  1235.     switch (kind) {
  1236.         case kGroup:
  1237.             theList = (**info).theList;
  1238.             groupArray = (**info).groupArray;
  1239.             SetPt(&theCell, 0, 0);
  1240.             numSelected = 0;
  1241.             while (LGetSelect(true, &theCell, theList)) {
  1242.                 numSelected++;
  1243.                 if (numSelected > 50) {
  1244.                     ErrorMessageNumber(kStrTooManyGroups);
  1245.                     return userCanceledErr;
  1246.                 }
  1247.                 cellDataLen = 2;
  1248.                 LGetCell(&index, &cellDataLen, theCell, theList);
  1249.                 strcpy(groupsStr, *gGroupNames + (*groupArray)[index].nameOffset);
  1250.                 theCell.v++;
  1251.                 if (numSelected > 1) TEInsert(", ", 2, newsgroups);
  1252.                 TEInsert(groupsStr, strlen(groupsStr), newsgroups);
  1253.             }
  1254.             if (numSelected > 5) {
  1255.                 err = TooManyGroupsDialog(numSelected);
  1256.                 if (err != noErr) return err;
  1257.             }
  1258.             break;
  1259.         case kSubject:
  1260.             strcpy(groupsStr, *gGroupNames + (**info).groupNameOffset);
  1261.             err = MyTESetText(groupsStr, strlen(groupsStr), newsgroups);
  1262.             if (err != noErr) return err;
  1263.             break;
  1264.         case kArticle:
  1265.             if ((**info).parentWindow != nil) {
  1266.                 parentInfo = (TWindow**)GetWRefCon((**info).parentWindow);
  1267.                 strcpy(groupsStr, *gGroupNames + (**parentInfo).groupNameOffset);
  1268.             }
  1269.             err = MyTESetText(groupsStr, strlen(groupsStr), newsgroups);
  1270.             if (err != noErr) return err;
  1271.             break;
  1272.     }
  1273.     return noErr;
  1274. }
  1275.  
  1276.  
  1277.  
  1278. /*----------------------------------------------------------------------------
  1279.     DrawIcon 
  1280.     
  1281.     Draw a single icon and checkmark in a message window panel area.
  1282.             
  1283.     Entry:    h = horizontal coord of icon.
  1284.             id = resource id of icon family.
  1285.             checked = true to draw check mark.
  1286. ----------------------------------------------------------------------------*/
  1287.  
  1288. static void DrawIcon (short h, short id, Boolean checked)
  1289. {
  1290.     Rect iconRect;
  1291.     TextStyle savedStyle;
  1292.     
  1293.     SetRect(&iconRect, h, kIconV, h+32, kIconV+32);
  1294.     PlotIconID(&iconRect, 0, ttNone, id);
  1295.     if (checked) {
  1296.         GetPortTextStyle(&savedStyle);
  1297.         TextFont(systemFont);
  1298.         TextSize(12);
  1299.         TextFace(bold);
  1300.         MoveTo(h - kCheckMarkDeltaH, kCheckMarkV);
  1301.         DrawChar(checkMark);
  1302.         SetPortTextStyle(&savedStyle);
  1303.     }
  1304. }
  1305.  
  1306.  
  1307.  
  1308. /*----------------------------------------------------------------------------
  1309.     QuoteText 
  1310.     
  1311.     Quote article text.
  1312.             
  1313.     Entry:    text = handle to text.
  1314.             start = beginning position in text to quote.
  1315.             end = ending position in text to quote.
  1316.             
  1317.     Exit:    function result = error code.
  1318.             text = handle to quoted text.
  1319.             
  1320.     The quote string is inserted at the beginning of each line in the
  1321.     range [start, end).
  1322. ----------------------------------------------------------------------------*/
  1323.  
  1324. static OSErr QuoteText (Handle text, long start, long end)
  1325. {
  1326.     long oldLen, len, deltaLen, quoteStringLen;
  1327.     char *p, *pEnd, *q;
  1328.     OSErr err = noErr;
  1329.  
  1330.     quoteStringLen = strlen(gPrefs.quoteString);
  1331.     oldLen = MyGetHandleSize(text);
  1332.     deltaLen = 0;
  1333.     for (p = *text + start, pEnd = *text + end; p < pEnd; p++)
  1334.         if (*p == CR) deltaLen += quoteStringLen;
  1335.     if (start < end && *(*text + end - 1) != CR) deltaLen += quoteStringLen;
  1336.     len = oldLen + deltaLen;
  1337.     
  1338.     err = MySetHandleSize(text, len);    
  1339.     if (err != noErr) return err;
  1340.     
  1341.     BlockMoveData(*text + start, *text + start + deltaLen, oldLen - start);
  1342.     
  1343.     p = *text + start + deltaLen;
  1344.     pEnd = *text + end + deltaLen;
  1345.     q = *text + start;
  1346.     while (p < pEnd) {
  1347.         BlockMoveData(gPrefs.quoteString, q, quoteStringLen);
  1348.         q += quoteStringLen;
  1349.         while (p < pEnd && *p != CR) *q++ = *p++;
  1350.         p++;
  1351.         if (p < pEnd) *q++ = CR;
  1352.     }
  1353.     
  1354.     return noErr;
  1355. }
  1356.  
  1357.  
  1358.  
  1359. /*----------------------------------------------------------------------------
  1360.     MoveHeaderLine 
  1361.     
  1362.     Move a header line from a text block to a TextEdit field.
  1363.             
  1364.     Entry:    text = handle to text block.
  1365.             key = header keyword.
  1366.             field = handle to TextEdit field.
  1367.             
  1368.     Exit:    function result = error code.
  1369. ----------------------------------------------------------------------------*/
  1370.  
  1371.  
  1372. static OSErr MoveHeaderLine (Handle text, char *key, TEHandle field)
  1373. {
  1374.     Handle header;
  1375.     OSErr err = noErr;
  1376.     
  1377.     err = FindHeaderHandle(text, key, &header);
  1378.     if (err != noErr) return err;
  1379.     if (header == nil) return noErr;
  1380.     MyDisposeHandle((**field).hText);
  1381.     (**field).hText = header;
  1382.     DeleteHeaderLine(text, key);
  1383.     return noErr;
  1384. }
  1385.  
  1386.  
  1387.  
  1388. /*----------------------------------------------------------------------------
  1389.     InitFieldInfo 
  1390.     
  1391.     Initialize field info for a new message window.
  1392.             
  1393.     Entry:    wind = pointer to message window.
  1394. ----------------------------------------------------------------------------*/
  1395.  
  1396. static void InitFieldInfo (WindowPtr wind)
  1397. {
  1398.     TWindow **info;
  1399.     short maxWidth, width;
  1400.     Str255 str;
  1401.     
  1402.     info = (TWindow**)GetWRefCon(wind);
  1403.     
  1404.     GetPString(kStrNewsgroups, str);
  1405.     maxWidth = StringWidth(str);
  1406.     GetPString(kStrTo, str);
  1407.     width = StringWidth(str);
  1408.     if (width > maxWidth) maxWidth = width;
  1409.     GetPString(kStrSubject, str);
  1410.     width = StringWidth(str);
  1411.     if (width > maxWidth) maxWidth = width;
  1412.     GetPString(kStrCc, str);
  1413.     width = StringWidth(str);
  1414.     if (width > maxWidth) maxWidth = width;
  1415.     GetPString(kStrBcc, str);
  1416.     width = StringWidth(str);
  1417.     if (width > maxWidth) maxWidth = width;
  1418.     GetPString(kStrReplyto, str);
  1419.     width = StringWidth(str);
  1420.     if (width > maxWidth) maxWidth = width;
  1421.     GetPString(kStrFollowupto, str);
  1422.     width = StringWidth(str);
  1423.     if (width > maxWidth) maxWidth = width;
  1424.     GetPString(kStrKeywords, str);
  1425.     width = StringWidth(str);
  1426.     if (width > maxWidth) maxWidth = width;
  1427.     GetPString(kStrDistribution, str);
  1428.     width = StringWidth(str);
  1429.     if (width > maxWidth) maxWidth = width;
  1430.     
  1431.     (**info).labelRight = maxWidth + kTextMargin + 4;
  1432.  
  1433.     RebuildFields(wind);
  1434.     InitCurField(wind);
  1435. }
  1436.  
  1437.  
  1438.  
  1439. /*----------------------------------------------------------------------------
  1440.     MyTENew 
  1441.     
  1442.     Create a TexTedit record.
  1443.             
  1444.     Exit:    function result = handle to new TE record.
  1445. ----------------------------------------------------------------------------*/
  1446.  
  1447. static TEHandle MyTENew (void)
  1448. {
  1449.     Rect r;
  1450.     TEHandle edit;
  1451.     
  1452.     SetRect(&r, 0, 0x7700, 500, 0x7fff);
  1453.     edit = TENew(&r, &r);
  1454.     (**edit).clickLoop = gAutoScrollUPP;
  1455.     return edit;
  1456. }
  1457.  
  1458.  
  1459.  
  1460. /*----------------------------------------------------------------------------
  1461.     InsertText 
  1462.     
  1463.     Insert new text (pasted or dragged) into a field.
  1464.             
  1465.     Entry:    wind = pointer to message window
  1466.             text = handle to text to insert.
  1467.             len = length of text to insert.
  1468.             edit = handle to TextEdit field for inserted text.
  1469.             offset = offset into TextEdit field for inserted text.
  1470.             deleteCurSel = true to delete current selected text.
  1471.             quote = true to insert text quoted.
  1472.             paste = true if paste, false if drag.
  1473.             
  1474.     Exit:    function result = error code.
  1475. ----------------------------------------------------------------------------*/
  1476.  
  1477. static OSErr InsertText (WindowPtr wind, Handle text, long len, TEHandle edit, 
  1478.     short offset, Boolean deleteCurSel, Boolean quote, Boolean paste)
  1479. {
  1480.     TWindow **info;
  1481.     short curField;
  1482.     TMsgFieldInfo **fields;
  1483.     TEHandle curEdit, quoteStringField;
  1484.     Handle newText = nil;
  1485.     char quoteString[11];
  1486.     long quoteStringLen, maxLen, quotedTextLen, newLen;
  1487.     OSErr err = noErr;
  1488.     unsigned char *p, *pEnd, *q;
  1489.     short n, tabStops, deletedTextLen, editIndex;
  1490.     Boolean isTabField, isQuoteStringField, isBodyField, isBodyOrSigField;
  1491.     Boolean extraSpaceDeleted, extraSpaceAddedInFront;
  1492.     
  1493.     info = (TWindow**)GetWRefCon(wind);
  1494.     curField = (**info).curField;
  1495.     fields = (**info).fields;
  1496.     curEdit = (*fields)[curField].edit;
  1497.     quoteStringField = (**info).quoteStringField;
  1498.     tabStops = (**info).tabStops;
  1499.     isTabField = edit == (**info).tabField;
  1500.     isQuoteStringField = edit == quoteStringField;
  1501.     isBodyField = edit == (**info).theTE;
  1502.     isBodyOrSigField = isBodyField || edit == (**info).signatureField;
  1503.     if (deleteCurSel && edit == curEdit) {
  1504.         deletedTextLen = (**curEdit).selEnd - (**curEdit).selStart;
  1505.         if (offset > (**edit).selEnd) offset -= deletedTextLen;
  1506.     } else {
  1507.         deletedTextLen = 0;
  1508.     }
  1509.  
  1510.     if (len == 0) return noErr;
  1511.     if (len >= 0x8000) goto exit1;
  1512.     
  1513.     maxLen = isTabField ? 2 : isQuoteStringField ? 11 : 0x7fff;
  1514.     if (!isBodyField) quote = false;
  1515.     
  1516.     newLen = 0;
  1517.     n = tabStops;
  1518.     for (p = (unsigned char*)*text, pEnd = p + len; p < pEnd; p++) {
  1519.         if (isTabField && (*p < '0' || *p > '9')) goto exit3;
  1520.         if (*p == '\t' && isBodyOrSigField) {
  1521.             newLen += n;
  1522.             n = tabStops;
  1523.         } else if (isPrintable(*p) || (isBodyField && *p == FF)) {
  1524.             newLen++;
  1525.             n--;
  1526.             if (n <= 0) n = tabStops;
  1527.         } else if (*p == CR) {
  1528.             newLen++;
  1529.             n = tabStops;
  1530.         } else if (!isBodyOrSigField) {
  1531.             goto exit3;
  1532.         }
  1533.     }
  1534.     
  1535.     if ((long)(**edit).teLength - deletedTextLen + newLen > maxLen)
  1536.     {
  1537.         if (isTabField || isQuoteStringField) {
  1538.             goto exit4;
  1539.         } else {
  1540.             goto exit1;
  1541.         }
  1542.     }
  1543.     
  1544.     err = MyNewHandle(newLen, &newText);
  1545.     if (err != noErr) goto exit2;
  1546.     
  1547.     n = tabStops;
  1548.     for (p = (unsigned char*)*text, q = (unsigned char*)*newText; p < pEnd; p++) {
  1549.         if (*p == '\t' && isBodyOrSigField) {
  1550.             while (n-- > 0) *q++ = ' ';
  1551.             n = tabStops;
  1552.         } else if (isPrintable(*p) || (isBodyField && *p == FF)) {
  1553.             *q++ = *p;
  1554.             n--;
  1555.             if (n <= 0) n = tabStops;
  1556.         } else if (*p == CR) {
  1557.             *q++ = *p;
  1558.             n = tabStops;
  1559.         }
  1560.     }
  1561.     
  1562.     MyHLock(newText);
  1563.     
  1564.     if (quote) {
  1565.         quoteStringLen = (**quoteStringField).teLength;
  1566.         BlockMoveData(*(**quoteStringField).hText, quoteString, quoteStringLen);
  1567.         p = (unsigned char*)*newText;
  1568.         pEnd = p + newLen;
  1569.         while (p < pEnd) {
  1570.              q = p;
  1571.              while (q < pEnd && *q != CR) q++;
  1572.              if (q - p + quoteStringLen > 80)
  1573.                  Wrap(newText, p - (unsigned char*)*newText, q - (unsigned char*)*newText);
  1574.              p = q+1;
  1575.         }
  1576.         p = (unsigned char*)*newText;
  1577.         pEnd = p + newLen;
  1578.         quotedTextLen = 0;
  1579.         while (p < pEnd) {
  1580.             quotedTextLen += quoteStringLen;
  1581.             q = p;
  1582.             while (q < pEnd && *q != CR) q++;
  1583.             if (q < pEnd) q++;
  1584.             quotedTextLen += q-p;
  1585.             p = q;
  1586.         }
  1587.         if ((long)(**edit).teLength - deletedTextLen + quotedTextLen > 0x7fff) goto exit1;
  1588.         if (deleteCurSel) TEDelete(curEdit);
  1589.         TESetSelect(offset, offset, edit);
  1590.         p = (unsigned char*)*newText;
  1591.         pEnd = p + newLen;
  1592.         while (p < pEnd) {
  1593.             TEInsert(quoteString, quoteStringLen, edit);
  1594.             q = p;
  1595.             while (q < pEnd && *q != CR) q++;
  1596.             if (q < pEnd) q++;
  1597.             TEInsert(p, q-p, edit);
  1598.             p = q;
  1599.         }
  1600.         newLen = quotedTextLen;
  1601.     } else {
  1602.         if (isTabField || isQuoteStringField) {
  1603.             if (deleteCurSel) TEDelete(curEdit);
  1604.             TESetSelect(offset, offset, edit);
  1605.             TEInsert(*newText, newLen, edit);
  1606.         } else {
  1607.             if (deleteCurSel) {
  1608.                 MyTEDelete(curEdit, false, &extraSpaceDeleted);
  1609.                 if (extraSpaceDeleted && edit == curEdit && 
  1610.                     offset > (**edit).selEnd) offset--;
  1611.             }
  1612.             TESetSelect(offset, offset, edit);
  1613.             MyTEPaste(*newText, newLen, edit, maxLen, &extraSpaceAddedInFront);
  1614.             if (extraSpaceAddedInFront) offset++;
  1615.         }
  1616.     }
  1617.     
  1618.     if (deleteCurSel && edit != curEdit) AdjustFieldHeight(wind, curEdit);
  1619.     
  1620.     if (!paste) {
  1621.         if (edit != curEdit) {
  1622.             editIndex = GetFieldIndex(wind, edit);
  1623.             if (editIndex >= 0) ChangeCurField(wind, editIndex);
  1624.         }
  1625.         TESetSelect(offset, offset + newLen, edit); 
  1626.     }
  1627.     
  1628.     MyDisposeHandle(newText);
  1629.     
  1630.     AdjustFieldHeight(wind, edit);
  1631.     (**info).changed = true;
  1632.     
  1633.     return noErr;
  1634.  
  1635. exit1:
  1636.  
  1637.     MyDisposeHandle(newText);
  1638.     ErrorMessageNumber(paste ? kStrScrapTooBigMaxIs32767 :
  1639.         kStrDragTextTooBigMaxIs32767);
  1640.     return userCanceledErr;
  1641.     
  1642. exit2:
  1643.  
  1644.     MyDisposeHandle(newText);
  1645.     return err;
  1646.     
  1647. exit3:
  1648.  
  1649.     MyDisposeHandle(newText);
  1650.     ErrorMessageNumber(paste ? kStrScrapBadChar : 
  1651.         kStrDragTextBadChar);
  1652.     return userCanceledErr;
  1653.     
  1654. exit4:
  1655.  
  1656.     MyDisposeHandle(newText);
  1657.     ErrorMessageNumber(paste ? kStrScrapTooBig :
  1658.         kStrDragTextTooBig);
  1659.     return userCanceledErr;
  1660. }
  1661.  
  1662.  
  1663.  
  1664. /*--------------------------------------------------------------------;
  1665.             gDestFieldOffset = -1;
  1666.             prevScrollDelta = 0;
  1667.             caretDrawn = false;
  1668.             break;
  1669.             
  1670.         case dragTrackingInWindow:
  1671.         
  1672.             if (!canAcceptDrag) break;
  1673.             GetDragMouse(theDrag, &where, nil);
  1674.             GlobalToLocal(&where);
  1675.             destField = FindFieldContainingPoint(wind, where, &fieldIndex);
  1676.             if (!textDrag && destField != (**info).newsgroupsField && 
  1677.                     destField != (**info).followuptoField) destField = nil;
  1678.             if (destField == nil) {
  1679.                 if (caretDrawn) {
  1680.                     DrawTECaret(gDestFieldOffset, gDestField);
  1681.                     caretDrawn = false;
  1682.                 }
  1683.                 if (hiliteField != nil) {
  1684.                     HideDragHilite(theDrag);
  1685.                     hiliteField = nil;
  1686.                 }
  1687.             } else {
  1688.                 if (leftSender && hiliteField != destField) {
  1689.                     if (caretDrawn) {
  1690.                         DrawTECaret(gDestFieldOffset, gDestField);
  1691.                         caretDrawn = false;
  1692.                     }
  1693.                     if (hiliteField != nil) HideDragHilite(theDrag);
  1694.                     rgn = NewRgn();
  1695.                     viewRect = (**destField).viewRect;
  1696.                     if (fieldIndex >= (**info).firstScrollingField) {
  1697.                         InsetRect(&viewRect, -kTextMargin, -3);
  1698.                     } else {
  1699.                         InsetRect(&viewRect, -2, -2);
  1700.                     }
  1701.                     RectRgn(rgn, &viewRect);
  1702.                     ShowDragHilite(theDrag, rgn, true);
  1703.                     DisposeRgn(rgn);
  1704.                     hiliteField = destField;
  1705.                 }
  1706.                 if (textDrag) {
  1707.                     offset = MyTEGetOffset(where, destField);
  1708.                     inSenderSelection = inSender && fieldIndex == (**info).curField && 
  1709.                         (**destField).selStart < offset && offset < (**destField).selEnd;
  1710.                     if (destField == gDestField && offset == gDestFieldOffset) {
  1711.                         if (!inSenderSelection) {
  1712.                             curTime = TickCount();
  1713.                             if (curTime - caretTime >= GetCaretTime()) {
  1714.                                 DrawTECaret(gDestFieldOffset, gDestField);
  1715.                                 caretDrawn = !caretDrawn;
  1716.                                 caretTime = curTime;
  1717.                             }
  1718.                         }
  1719.                     } else {
  1720.                         if (caretDrawn) {
  1721.                             DrawTECaret(gDestFieldOffset, gDestField);
  1722.                             caretDrawn = false;
  1723.                         }
  1724.                         if (!inSenderSelection) {
  1725.                             DrawTECaret(offset, destField);
  1726.                             caretDrawn = true;
  1727.                             caretTime = TickCount();
  1728.                         }
  1729.                     }
  1730.                 }
  1731.             }
  1732.             gDestField = destField;
  1733.             gDestFieldOffset = offset;
  1734.             
  1735.             GetViewRect(wind, &viewRect);
  1736.             vScroll = (**info).vScroll;
  1737.             val = GetControlValue(vScroll);
  1738.             max = GetControlMaximum(vScroll);
  1739.             if (where.v < viewRect.top && val > 0) {
  1740.                 scrollDelta = -1;
  1741.             } else if (where.v > viewRect.bottom && val < max) {
  1742.                 scrollDelta = +1;
  1743.             } else {
  1744.                 scrollDelta = 0;
  1745.             }
  1746.             if (scrollDelta == prevScrollDelta) {
  1747.                 if (scrollDelta != 0 && TickCount() - scrollTickCount < 10) 
  1748.                     scrollDelta = 0;
  1749.             } else {
  1750.                 prevScrollDelta = scrollDelta;
  1751.                 scrollDelta = 0;
  1752.                 scrollTickCount = TickCount();
  1753.             } 
  1754.             if (scrollDelta != 0) {
  1755.                 Scroll(wind, -scrollDelta);
  1756.                 SetControlValue(vScroll, val + scrollDelta);
  1757.             }
  1758.             
  1759.             break;
  1760.             
  1761.         case dragTrackingLeaveWindow:
  1762.         
  1763.             if (caretDrawn) DrawTECaret(gDestFieldOffset, gDestField);
  1764.             if (hiliteField != nil) HideDragHilite(theDrag);
  1765.             hiliteField = nil;
  1766.             gDestField = nil;
  1767.             gDestFieldOffset = -1;
  1768.             prevScrollDelta = 0;
  1769.             caretDrawn = false;
  1770.             break;
  1771.     
  1772.         case dragTrackingLeaveHandler:
  1773.         
  1774.             break;
  1775.             
  1776.     }
  1777.  
  1778.     return noErr;
  1779. }
  1780.  
  1781.  
  1782.  
  1783. /*----------------------------------------------------------------------------
  1784.     MergeTextScrapData 
  1785.     
  1786.     Merge two blocks of text scrap data.
  1787.     
  1788.     Entry:    data1 = handle to first scrap data.
  1789.             data2 = handle to second scrap data.
  1790.             
  1791.     Exit:    function result = error code.
  1792.             data1 = handle to merged scrap data.
  1793. ----------------------------------------------------------------------------*/
  1794.  
  1795. static OSErr MergeTextScrapData (Handle data1, Handle data2)
  1796. {
  1797.     long len1, len2;
  1798.     OSErr err = noErr;
  1799.     
  1800.     len1 = MyGetHandleSize(data1);
  1801.     len2 = MyGetHandleSize(data2);
  1802.     err = MySetHandleSize(data1, len1 + len2 + 1);
  1803.     if (err != noErr) return err;
  1804.     *(*data1 + len1) = ' ';
  1805.     BlockMoveData(*data2, *data1 + len1 + 1, len2);
  1806.     return noErr;
  1807. }
  1808.  
  1809.  
  1810.  
  1811. /*----------------------------------------------------------------------------
  1812.     GetTextDragScrapData 
  1813.     
  1814.     Get the text scrap data at the end of a drag.
  1815.     
  1816.     Entry:    theDrag = drag reference.
  1817.             
  1818.     Exit:    function result = error code.
  1819.             *dragData = handle to drag data.
  1820. -------------)) p++;
  1821.     fieldIsAllWhiteSpace = p >= pEnd;
  1822.     while (p < pEnd) {
  1823.         if (*p == ' ' || *p == ',') {
  1824.             while (p < pEnd && (*p == ' ' || *p == ',')) p++;
  1825.             if (p < pEnd) numOldGroups++;
  1826.         }
  1827.         p++;
  1828.     }
  1829.     if (!fieldIsAllWhiteSpace) numOldGroups++;
  1830.     
  1831.     if (numNewGroups + numOldGroups > 5) {
  1832.         err = TooManyGroupsDialog(numNewGroups + numOldGroups);
  1833.         if (err != noErr) goto exit;
  1834.     }
  1835.     
  1836.     if (textLen + lenNewGroups > 0x7fff) {
  1837.         ErrorMessageNumber(kStrGroupDragTooBigMaxIs32767);
  1838.         err = userCanceledErr;
  1839.         goto exit;
  1840.     }
  1841.     
  1842.     dataPos = firstGroupDataPos;
  1843.     TESetSelect(0x7fff, 0x7fff, gFinalDestField);
  1844.     for (i = 0; i < numGroups; i++) {
  1845.         groupNameLen = *(*gDragData + dataPos);
  1846.         dataPos++;
  1847.         BlockMoveData(*gDragData + dataPos, groupName, groupNameLen);
  1848.         dataPos += groupNameLen;
  1849.         dataPos = ((dataPos + 3) >> 2) << 2;
  1850.         *(groupName + groupNameLen) = 0;
  1851.         if (!MyIsASubstringHandle(text, groupName)) {
  1852.             if (!fieldIsAllWhiteSpace) TEInsert(", ", 2, gFinalDestField);
  1853.             fieldIsAllWhiteSpace = false;
  1854.             TEInsert(groupName, groupNameLen, gFinalDestField);
  1855.         }
  1856.         dataPos += 3*sizeof(long);
  1857.         numUnreadPairs = *(long*)(*gDragData + dataPos);
  1858.         dataPos += sizeof(long);
  1859.         dataPos += numUnreadPairs * 2 * sizeof(long);
  1860.     }
  1861.     
  1862.     (**info).changed = true;
  1863.     
  1864.     AdjustFieldHeight(gDragDestWindow, gFinalDestField);
  1865.  
  1866. exit:
  1867.  
  1868.     MyDisposeHandle(gDragData);
  1869.     gDragData = nil;
  1870.     SetPort(port);
  1871.     return err;
  1872. }
  1873.  
  1874.  
  1875.  
  1876. /*----------------------------------------------------------------------------
  1877.     HandleReceive 
  1878.     
  1879.     Drag Manager receive handler for message windows.
  1880.     
  1881.     Entry:    wind = pointer to message window.
  1882.             handlerRefCon = reference constant (nil).
  1883.             theDrag = drag reference.
  1884.             
  1885.     Exit:    function result = error code.
  1886.     
  1887.     Note: No user interaction or network transactions are permitted in
  1888.     this function.
  1889. ----------------------------------------------------------------------------*/
  1890.  
  1891. static pascal OSErr HandleReceive (WindowPtr wind, void *handlerRefCon, 
  1892.     DragReference theDrag)
  1893. {
  1894.     Boolean textDrag;
  1895.     DragAttributes attributes;
  1896.     short mouseDownModifiers, mouseUpModifiers;
  1897.     TWindow **info;
  1898.     TMsgFieldInfo **fields;
  1899.     short curField, selStart, selEnd;
  1900.     TEHandle edit;
  1901.  
  1902.     if (gLongOperation || gInDialog || gDragErr != noErr ||
  1903.         gDestField == nil || !CanAcceptDrag(theDrag, &textDrag)) goto exit;
  1904.     
  1905.     gDragDestWindow = wind;
  1906.     gFinalDestField = gDestField;
  1907.     
  1908.     if (textDrag) {
  1909.         gFinalDestFieldOffset = gDestFieldOffset;
  1910.         gDragErr = GetTextDragScrapData(theDrag, &gDragData);
  1911.         if (gDragErr != noErr) goto exit;
  1912.         gDragTextCopy = true;
  1913.         GetDragAttributes(theDrag, &attributes);
  1914.         GetDragModifiers(theDrag, nil, &mouseDownModifiers, &mouseUpModifiers);
  1915.         gDragInsertTextQuoted = (mouseDownModifiers & shiftKey) != 0 ||
  1916.                 (mouseUpModifiers & shiftKey) != 0;
  1917.         if ((attributes & dragInsideSenderWindow) != 0) {
  1918.             gDragTextCopy = (mouseDownModifiers & optionKey) != 0 ||
  1919.                 (mouseUpModifiers & optionKey) != 0;
  1920.             info = (TWindow**)GetWRefCon(gDragDestWindow);
  1921.             fields = (**info).fields;
  1922.             curField = (**info).curField;
  1923.             edit = (*fields)[curField].edit;
  1924.             selStart = (**edit).selStart;
  1925.             selEnd = (**edit).selEnd;
  1926.             if (edit == gFinalDestField) {
  1927.                 if ((gDragTextCopy && selStart < gFinalDestFieldOffset && 
  1928.                         gFinalDestFieldOffset < selEnd) ||
  1929.                     (!gDragTextCopy && selStart <= gFinalDestFieldOffset &&
  1930.                         gFinalDestFieldOffset <= selEnd))
  1931.                 {
  1932.                     gDragErr = userCanceledErr;
  1933.                     goto exit;
  1934.                 }
  1935.             }
  1936.         }
  1937.         gDragPostProcessor = HandleTextDragReceivePostProcessor;
  1938.     } else {
  1939.         gDragErr = GetGroupDragScrapData(theDrag, &gDragData);
  1940.         if (gDragErr != noErr) goto exit;
  1941.         gDragPostProcessor = HandleGroupDragReceivePostProcessor;
  1942.     }
  1943.     
  1944.     return noErr;
  1945.     
  1946. exit:
  1947.  
  1948.     HandleTracking(dragTrackingLeaveWindow, wind, handlerRefCon, theDrag);
  1949.     return dragNotAcceptedErr;
  1950. }
  1951.  
  1952.  
  1953.  
  1954. /*----------------------------------------------------------------------------
  1955.     MakeNewWindow 
  1956.     
  1957.     Create and initialize a new message window.
  1958.             
  1959.     Exit:    function result = error code.
  1960.             *theWindow = pointer to new window.
  1961. ----------------------------------------------------------------------------*/
  1962.  
  1963. static OSErr MakeNewWindow (WindowPtr *thfNum);
  1964.     if (err != noErr) goto exit;
  1965.     err = GetEOF(fRefNum, &length);
  1966.     if (err != noErr) goto exit;
  1967.     if (length > 0x7fff) {
  1968.         length = 0x7fff;
  1969.         *truncated = true;
  1970.     }
  1971.     
  1972.     err = MySetHandleSize(hText, length);
  1973.     if (err != noErr) goto exit;
  1974.     
  1975.     state = MyHGetState(hText);
  1976.     err = FSRead(fRefNum, &length, *hText);
  1977.     MyHSetState(hText, state);
  1978.     if (err != noErr) goto exit;
  1979.     
  1980.     MyFSClose(fRefNum, nil);
  1981.     return noErr;
  1982.     
  1983. exit:
  1984.  
  1985.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  1986.     return err;
  1987. }
  1988.  
  1989.  
  1990.  
  1991. /*----------------------------------------------------------------------------
  1992.     ReadOneHandle 
  1993.     
  1994.     Read one relocatable block from a saved message window resource fork.
  1995.             
  1996.     Entry:    id = resource id.
  1997.     
  1998.     Exit:    function result = error code.
  1999.             *theHandle = handle to block, or nil if resource does not exist.
  2000. ----------------------------------------------------------------------------*/
  2001.  
  2002. static OSErr ReadOneHandle (short id, Handle *theHandle)
  2003. {
  2004.     Handle h = nil;
  2005.     OSErr err = noErr;
  2006.     
  2007.     err = MyGet1Resource('TEXT', id, &h);
  2008.     if (err == resNotFound) {
  2009.         *theHandle = nil;
  2010.         return noErr;
  2011.     }
  2012.     if (err != noErr) return err;
  2013.     MyDetachResource(h);
  2014.     *theHandle = h;
  2015.     return noErr;
  2016. }
  2017.  
  2018.  
  2019.  
  2020. /*----------------------------------------------------------------------------
  2021.     ReadOneTEField 
  2022.     
  2023.     Read one saved TE field from a saved message window resource fork.
  2024.             
  2025.     Entry:    field = handle to TE field.
  2026.             id = resource id.
  2027.     
  2028.     Exit:    function result = error code.
  2029.             *truncated = true if field >= 32K and truncated.
  2030. ----------------------------------------------------------------------------*/
  2031.  
  2032. static OSErr ReadOneTEField (TEHandle field, short id, Boolean *truncated)
  2033. {
  2034.     Handle hText;
  2035.     long len;
  2036.     OSErr err = noErr;
  2037.  
  2038.     *truncated = false;
  2039.     err = ReadOneHandle(id, &hText);
  2040.     if (err != noErr) return err;
  2041.     if (hText == nil) return noErr;
  2042.     len = MyGetHandleSize(hText);
  2043.     if (len > 0x7fff) {
  2044.         len = 0x7fff;
  2045.         *truncated = true;
  2046.     }
  2047.     MyHLock(hText);
  2048.     err = MyTESetText(*hText, len, field);
  2049.     MyDisposeHandle(hText);
  2050.     return err;
  2051. }
  2052.  
  2053.  
  2054.  
  2055. /*----------------------------------------------------------------------------
  2056.     ReadMessageWindResourceFork 
  2057.     
  2058.     Read the resource fork of a saved message window.
  2059.             
  2060.     Entry:    wind = pointer to message window.
  2061.             fSpec = pointer to file spec.
  2062.     
  2063.     Exit:    function result = error code.
  2064.             *truncated = true if field >= 32K and truncated.
  2065.             *pos = saved window position.
  2066. ----------------------------------------------------------------------------*/
  2067.  
  2068. static OSErr ReadMessageWindResourceFork (WindowPtr wind, FSSpec *fSpec,
  2069.     Boolean *truncated, TSavedWindPos *pos)
  2070. {
  2071.     TWindow **info;
  2072.     OSErr err = noErr;
  2073.     short refNum = 0;
  2074.     TMiscMessageWindowInfo **miscInfo = nil;
  2075.     CStr255 tabStopsValueStr;
  2076.     Handle theHandle, h;
  2077.     Boolean t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13;
  2078.     
  2079.     *truncated = false;
  2080.     pos->valid = false;
  2081.     
  2082.     info = (TWindow**)GetWRefCon(wind);
  2083.     
  2084.     err = MyFSpOpenResFile(fSpec, fsRdPerm, &refNum);
  2085.     if (err == eofErr || err == fnfErr) return noErr;
  2086.     if (err != noErr) goto exit;
  2087.     
  2088.     err = MyGet1Resource('MISC', 128, &miscInfo);
  2089.     if (err == noErr) {
  2090.         (**info).newsIcon = (**miscInfo).newsIcon;
  2091.         (**info).mailIcon = (**miscInfo).mailIcon;
  2092.         (**info).selfIcon = (**miscInfo).selfIcon;
  2093.         (**info).tabEnabled = (**miscInfo).tabEnabled;
  2094.         (**info).wrapOnSend = (**miscInfo).wrapOnSend;
  2095.         (**info).tabStops = (**miscInfo).tabStops;
  2096.         MyReleaseResource(miscInfo);
  2097.     }
  2098.     
  2099.     SetControlValue((**info).tabCheckbox, (**info).tabEnabled ? 1 : 0);
  2100.     SetControlValue((**info).wrapCheckbox, (**info).wrapOnSend ? 1 : 0);
  2101.     sprintf(tabStopsValueStr, "%d", (**info).tabStops);
  2102.     err = MyTESetText(tabStopsValueStr, strlen(tabStopsValueStr), (**info).tabField);
  2103.     if (err != noErr) goto exit;
  2104.     
  2105.     err = ReadOneTEField((**info).quoteStringField, kQuoteStringResID, &t1);
  2106.     if (err != noErr) goto exit;
  2107.     err = ReadOneTEField((**info).newsgroupsField, kNewsgroupsResID, &t2);
  2108.     if (err != noErr) goto exit;
  2109.     err = ReadOneTEField((**info).toField, kToResID, &t3);
  2110.     if (err != noErr) goto exit;
  2111.     err = ReadOneTEField((**info).subjectField, kSubjectResID, &t4);
  2112.     if (err != noErr) goto exit;
  2113.     err = ReadOneTEField((**info).ccFiftKey) != 0, edit);
  2114.                 if (fieldIndex >= (**info).firstScrollingField) {
  2115.                     err = CommandClick(wind, edit, oldSelStart, oldSelEnd, modifiers);
  2116.                     if (err != noErr) return err;
  2117.                 }
  2118.             } else if (trashed) {
  2119.                 MyTEDelete(edit, false, &extraSpaceDeleted);
  2120.                 AdjustFieldHeight(wind, edit);
  2121.                 (**info).changed = true;
  2122.             }
  2123.         }
  2124.     }
  2125.     return noErr;
  2126. }
  2127.  
  2128.  
  2129.  
  2130. /*----------------------------------------------------------------------------
  2131.     Draggable
  2132.     
  2133.     Determine whether a mouse down event is on a draggable object in a 
  2134.     message window.
  2135.     
  2136.     Entry:    wind = pointer to message window.
  2137.             where = location of mouse down event, in local coordinates.
  2138.             modifiers = modifiers field from event record.
  2139.             
  2140.     Exit:    function result = true if object is draggable.
  2141. ----------------------------------------------------------------------------*/
  2142.  
  2143. static Boolean Draggable (WindowPtr wind, Point where, short modifiers)
  2144. {
  2145.     TWindow **info;
  2146.     TMsgFieldInfo **fields;
  2147.     short curField;
  2148.     TEHandle edit;
  2149.     
  2150.     info = (TWindow**)GetWRefCon(wind);
  2151.     fields = (**info).fields;
  2152.     curField = (**info).curField;
  2153.     edit = (*fields)[curField].edit;
  2154.     return PtInTEHiliteRgn(where, edit);
  2155. }
  2156.  
  2157.  
  2158.  
  2159. /*----------------------------------------------------------------------------
  2160.     Key 
  2161.     
  2162.     Handle a key down event for a message window.
  2163.             
  2164.     Entry:    wind = pointer to message window.
  2165.             theChar = ASCII code of key.
  2166.             theKey = keyboard code of key.
  2167.             modifiers = modifiers field from event record.
  2168.             
  2169.     Exit:    function result = error code.
  2170. ----------------------------------------------------------------------------*/
  2171.  
  2172. static OSErr Key (WindowPtr wind, unsigned char theChar, unsigned char theKey, 
  2173.     short modifiers)
  2174. {
  2175.     TWindow **info;
  2176.     TMsgFieldInfo **fields;
  2177.     TEHandle edit;
  2178.     short curField, numFields;
  2179.     short selStart, selEnd, teLength;
  2180.     Handle text;
  2181.     short h;
  2182.     Rect r;
  2183.     short numSpace;
  2184.     char *p;
  2185.     short col;
  2186.     Boolean shift, option, command, tabEnabled;
  2187.     ControlHandle vScroll;
  2188.     short tabStops;
  2189.     long maxLen;
  2190.     Boolean isTabField, isQuoteStringField, isBodyField, isSigField, isArrow;
  2191.     OSErr err = noErr;
  2192.     Boolean extraSpaceDeleted;
  2193.     short scrollIntoView;
  2194.     
  2195.     shift = (modifiers & shiftKey) != 0;
  2196.     option = (modifiers & optionKey) != 0;
  2197.     command = (modifiers & cmdKey) != 0;
  2198.  
  2199.     info = (TWindow**)GetWRefCon(wind);
  2200.     vScroll = (**info).vScroll;
  2201.     fields = (**info).fields;
  2202.     curField = (**info).curField;
  2203.     numFields = (**info).numFields;
  2204.     tabEnabled = (**info).tabEnabled;
  2205.     tabStops = (**info).tabStops;
  2206.     edit = (*fields)[curField].edit;
  2207.     selStart = (**edit).selStart;
  2208.     selEnd = (**edit).selEnd;
  2209.     teLength = (**edit).teLength;
  2210.     text = (**edit).hText;
  2211.     
  2212.     isTabField = edit == (**info).tabField;
  2213.     isQuoteStringField = edit == (**info).quoteStringField;
  2214.     isBodyField = edit == (**info).theTE;
  2215.     isSigField = edit == (**info).signatureField;
  2216.     isArrow = IsArrowKey(theChar);
  2217.  
  2218.     if (command) {
  2219.         if (theChar == '1' || theChar == '2' || theChar == '3') {
  2220.             switch (theChar) {
  2221.                 case '1':
  2222.                     (**info).newsIcon = !(**info).newsIcon;
  2223.                     h = kNewsIconH;
  2224.                     ChangeDisplayedFields(wind);
  2225.                     break;
  2226.                 case '2':
  2227.                     (**info).mailIcon = !(**info).mailIcon;
  2228.                     ChangeDisplayedFields(wind);
  2229.                     h = kMailIconH;
  2230.                     break;
  2231.                 case '3':
  2232.                     (**info).selfIcon = !(**info).selfIcon;
  2233.                     h = kSelfIconH;
  2234.                     break;
  2235.             }
  2236.             SetRect(&r, h - kCheckMarkDeltaH, kIconV, h - 1, kIconV + 32);
  2237.             InvalRect(&r);
  2238.              HiliteControl((**info).sendButton, 
  2239.                  (**info).newsIcon || (**info).mailIcon || (**info).selfIcon ? 0 : 255);
  2240.              return noErr;
  2241.         } else if (!isArrow) {
  2242.             SysBeep(0);
  2243.             return noErr;
  2244.         }
  2245.     }
  2246.  
  2247.     /* Note: The following code has been commented out to disable the keypad shortcuts
  2248.        in message windows, at the request of J.P. Kuypers, who would like to use the
  2249.        keypad to type numbers on his Azerty keyboard. If anyone complains, I can add
  2250.        a new pref for this. */
  2251.        
  2252.     /*
  2253.         if (gPrefs.keypadShortcuts && IsKeypadKey(theChar, theKey, &keypadKey)) {
  2254.             switch (keypadKey) {
  2255.                 case kKeypadEqualKey:
  2256.                     DoSelectAll(wind);
  2257.                     return noErr;
  2258.                 case kKeypadStarKey:
  2259.                     return DoClose(wind);
  2260.                 case kKeypad1Key:
  2261.                     ScrollAction(vScroll, kScrollToEnd);
  2262.                     return noErr;
  2263.                 case kKeypad2Key:
  2264.                     ScrollAction(vScroll, inDownButton);
  2265.                     return noErr;
  2266.                 case kKeypad3Key:
  2267.                     ScrollAction(vScroll, inPageDown);
  2268.                     return noErr;
  2269.                 case kKeypad7Key:
  2270.                     ScrollAction(vScroll, kScrollToHome);
  2271.                     return noErr;
  2272.                 case kKeypad8Key:
  2273.                     ScrollAction(vScroll, inUpButton);
  2274.                     return noErr;
  2275.                 case kKeypad9Key:
  2276.                     ScrollAction(vScroll, inPageUp);
  2277.                     return noErr;
  2278.                 default:
  2279.                     SysBeep(0);
  2280.                     return noErr;
  2281.             }
  2282.         }
  2283.     */
  2284.     
  2285.     if (theChar == 0x0C && theKey != 0x79 && (modifiers & controlKey) != 0) {
  2286.         /* Control-L */
  2287.         DoInsertSpoilerCharacter(wind);
  2288.         return noErr;
  2289.     }
  2290.     
  2291.     if (theChar == pageUpKey) {
  2292.         ScrollAction(vScroll, inPageUp);
  2293.         return noErr;
  2294.     }
  2295.     if (theChar == pageDownKey) {
  2296.         ScrollAction(vScroll, inPageDown);
  2297.         return noErr;
  2298.     }
  2299.     if (theChar == homeKey) {
  2300.         ScrollAction(vScroll, kScrollToHome);
  2301.         return noErr;
  2302.     }
  2303.     if (theChar == endKey) {
  2304.         ScrollAction(vScroll, kScrollToEnd);
  2305.         return noErr;
  2306.     }
  2307.     
  2308.     if (theChar == forwardDelKey) {
  2309.         if (selStart < selEnd) {
  2310.             theChar = deleteKey;
  2311.         } else if (selEnd < teLength) {
  2312.             (**edit).selEnd = selEnd + 1;
  2313.             TEDelete(edit);
  2314.             goto exit1;
  2315.         } else {
  2316.             SysBeep(0);
  2317.             return noErr;
  2318.         }
  2319.     }
  2320.     
  2321.     if (theChar == deleteKey && selStart < selEnd) {
  2322.         MyTEDelete(edit, false, &extraSpaceDeleted);
  2323.         goto exit1;
  2324.     }
  2325.     
  2326.     if (theChar == tabKey) {
  2327.         if (tabEnabled && !shift && !option && (isBodyField || isSigField)) {
  2328.             if (tabStops == 0) {
  2329.                 numSpace = 1;
  2330.             } else {
  2331.                 p = *text + selStart - 1;
  2332.                 col = 0;
  2333.                 while (p >= *text && *p != CR) {
  2334.                     p--;
  2335.                     col++;
  2336.                 }
  2337.                 numSpace = tabStops - (col % tabStops);
  2338.             }
  2339.             if ((long)(**edit).teLength - (long)(**edit).selEnd + 
  2340.                 (long)(**edit).selStart + numSpace > 0x7fff) goto exit2;
  2341.             while (numSpace--) TEKey(' ', edit);
  2342.             goto exit1;
  2343.         } else {
  2344.             if (shift) {
  2345.                 curField--;
  2346.                 if (curField < 0) curField = numFields - 1;
  2347.             } else {
  2348.                 curField++;
  2349.                 if (curField >= numFields) curField = 0;
  2350.             }
  2351.             edit = (*fields)[curField].edit;
  2352.             TESetSelect(0, 0x7fff, edit);
  2353.             ChangeCurField(wind, curField);
  2354.             ScrollSelectionIntoView(wind);
  2355.             return noErr;
  2356.         }
  2357.     }
  2358.     
  2359.     if (theChar != deleteKey && !isArrow) {
  2360.         if (!isPrintable(theChar) && theChar != CR ||
  2361.             theChar == CR && (isTabField || isQuoteStringField) ||
  2362.             isTabField && !(theChar >= '0' && theChar <= '9'))
  2363.         {
  2364.             SysBeep(0);
  2365.             return noErr;
  2366.         }
  2367.     }
  2368.     
  2369.     maxLen = isTabField ? 2 : isQuoteStringField ? 11 : 0x7fff;
  2370.     if (isPrintable(theChar) || theChar == CR) {
  2371.         if ((long)(**edit).teLength - (long)(**edit).selEnd + 
  2372.             (long)(**edit).selStart + 1 > maxLen) goto exit2;
  2373.     }
  2374.     
  2375.     if (isArrow) {
  2376.         TEArrowKey(theChar, modifiers, edit, GetPageHeight(wind), &gPrevEvent, 
  2377.             &scrollIntoView);
  2378.         ScrollRangeIntoView(wind, scrollIntoView, scrollIntoView);
  2379.     } else {
  2380.         TEKey(theChar, edit);
  2381.     }
  2382.     
  2383. exit1:
  2384.     
  2385.     if (!isArrow) AdjustCurFieldHeight(wind);
  2386.     if (!isArrow && !isTabField && !isQuoteStringField) (**info).changed = true;
  2387.     return noErr;
  2388.     
  2389. exit2:
  2390.  
  2391.     if (!isTabField && !isQuoteStringField) {
  2392.         ErrorMessageNumber(kStrNoMoreChars);
  2393.     } else {
  2394.         ErrorMessageNumber(kStrNoMoreTyping);
  2395.     }
  2396.     return userCanceledErr;
  2397. }
  2398.  
  2399.  
  2400.  
  2401. /*----------------------------------------------------------------------------
  2402.     Grow 
  2403.     
  2404.     Handle a mouse down event in the grow box of a message window.
  2405.     
  2406.     Entry:    wind = pointer to message window.
  2407.             where = location of mouse down event, in global coordinates.
  2408.             
  2409.     Exit:    function result = error code.
  2410. ----------------------------------------------------------------------------*/
  2411.  
  2412. static OSErr Grow (WindowPtr wind, Point where)
  2413. {
  2414.     Rect sizeRect;
  2415.     long size;
  2416.     short width, height;
  2417.     TWindow **info;
  2418.     
  2419.     SetRect(&sizeRect, MinWidth(wind), MinHeight(wind), 0x7fff, 0x7fff);
  2420.     size = GrowWindow(wind, where, &sizeRect);
  2421.     
  2422.     if (size  != 0) {
  2423.         width = LoWord(size);
  2424.         height = HiWord(size);
  2425.         FixHeight(wind, &height);
  2426.         SizeWindow(wind, width, height, false);
  2427.         ResizeContents(wind);
  2428.         info = (TWindow**)GetWRefCon(wind);
  2429.         (**info).windPosValid = true;
  2430.         (**info).movedSinceLastSave = true;
  2431.     }
  2432.     
  2433.     return noErr;
  2434. }
  2435.  
  2436.  
  2437.  
  2438. /*----------------------------------------------------------------------------
  2439.     Zoom
  2440.     
  2441.     Zoom a message window.
  2442.     
  2443.     Entry:    wind = pointer to message window.
  2444.             zoomDir = zoom direction = inZoomIn or inZoomOut.
  2445.             
  2446.     Exit:    function result = error code.
  2447. ----------------------------------------------------------------------------*/
  2448.  
  2449. static OSErr Zoom (WindowPtr wind, short zoomDir)
  2450. {
  2451.     TWindow **info;
  2452.     short width, height;
  2453.     Rect zoomRect;    
  2454.     WStateData **wState;
  2455.  
  2456.     info = (TWindow**)GetWRefCon(wind);
  2457.     wState = (WStateData**)((WindowPeek)wind)->dataHandle;
  2458.     if (zoomDir == inZoomOut) {
  2459.         width = 80*CharWidth('W') + 2*kTextMargin + 18;
  2460.         height = 0x7fff;
  2461.         CalculateZoomRect(wind, width, height, &zoomRect, gPrefs.dontCoverFinderIcons);
  2462.         height = zoomRect.bottom - zoomRect.top;
  2463.         FixHeight(wind, &height);
  2464.         zoomRect.bottom = zoomRect.top + height;
  2465.         (**wState).stdState = zoomRect;
  2466.         if (WindRectEqualRect(wind, &zoomRect)) return noErr;
  2467.     }
  2468.     
  2469.     EraseRect(&wind->portRect);
  2470.     ZoomWindow(wind, zoomDir, false);
  2471.     ResizeContents(wind);
  2472.     (**info).windPosValid = true;
  2473.     (**info).movedSinceLastSave = true;
  2474.     return noErr;
  2475. }
  2476.  
  2477.  
  2478.  
  2479. /*----------------------------------------------------------------------------
  2480.     Command 
  2481.     
  2482.     Handle a command for a message window.
  2483.             
  2484.     Entry:    wind = pointer to message window.
  2485.             menu = the menu.
  2486.             item = the item.
  2487.             modifiers = modifiers field from event record.
  2488.     
  2489.     Exit:    function result = error code.
  2490. ----------------------------------------------------------------------------*/
  2491.  
  2492. static OSErr Command (WindowPtr wind, short menu, short item, short modifiers)
  2493. {
  2494.     OSErr err = noErr;
  2495.  
  2496.     switch (menu) {
  2497.                 
  2498.         case kFileMenu:
  2499.         
  2500.             switch (item) {
  2501.                 case kSaveItem:
  2502.                     err = DoSave(wind, modifiers);
  2503.                     break;
  2504.                 case kSaveAsItem:
  2505.                     err = DoSaveAs(wind);
  2506.                     break;
  2507.                 case kPrintItem:
  2508.                     err = DoPrint(wind, modifiers);
  2509.                     break;
  2510.             }
  2511.             break;
  2512.             
  2513.         case kEditMenu:
  2514.  
  2515.             switch (item) {
  2516.                 case kCutItem:
  2517.                     DoCut(wind);
  2518.                     break;
  2519.                 case kCopyItem:
  2520.                     DoCopy(wind);
  2521.                     break;
  2522.                 case kPasteItem:
  2523.                     err = DoPaste(wind, (modifiers & shiftKey) != 0);
  2524.                     break;
  2525.                 case kPasteAsQuotationItem:
  2526.                     DoPaste(wind, true);
  2527.                     break;
  2528.                 case kClearItem:
  2529.                     DoClear(wind);
  2530.                     break;
  2531.                 case kSelectAllItem:
  2532.                     DoSelectAll(wind);
  2533.                     break;
  2534.                 case kFindItem:
  2535.                     err = DoFind(wind);
  2536.                     break;
  2537.                 case kFindAgainItem:
  2538.                     err = DoFindAgain(wind);
  2539.                     break;
  2540.                 case kEnterSelectionItem:
  2541.                     err = DoEnterSelection(wind);
  2542.                     break;
  2543.                 case kShowHideDetailsItem:
  2544.                     DoShowHideDetails(wind);
  2545.                     break;
  2546.                 case kRot13Item:
  2547.                     DoRot13(wind);
  2548.                     break;
  2549.                 case kInsertSpoilerCharacterItem:
  2550.                     DoInsertSpoilerCharacter(wind);
  2551.                     break;
  2552.                 case kWrapItem:
  2553.                     DoWrap(wind);
  2554.                     break;
  2555.                 case kUnwrapItem:
  2556.                     DoUnwrap(wind);
  2557.                     break;
  2558.             }
  2559.             break;
  2560.  
  2561.         case kNewsMenu:
  2562.         
  2563.             switch (item) {
  2564.                 case kSendMessageItem:
  2565.                     err = DoSendMessage(wind);
  2566.                     break;
  2567.             }
  2568.             break;
  2569.      }
  2570.      
  2571.      return err;
  2572. }
  2573.  
  2574.  
  2575.  
  2576. /*----------------------------------------------------------------------------
  2577.     Close 
  2578.     
  2579.     Close a message window.
  2580.             
  2581.     Entry:    wind = pointer to message window.
  2582.     
  2583.     Exit:    function result = error code.
  2584. ----------------------------------------------------------------------------*/
  2585.  
  2586. static OSErr Close (WindowPtr wind)
  2587. {
  2588.     TWindow **info;
  2589.     DialogPtr dlg = nil;
  2590.     short item;
  2591.     OSErr err = noErr;
  2592.  
  2593.     info = (TWindow**)GetWRefCon(wind);
  2594.  
  2595.     if ((**info).changed) {
  2596.         err = MyGetNewDialog(kAskSendOrSaveAlert, 1, 3, &dlg);
  2597.         if (err != noErr) return err;
  2598.         SetItemKeyEquivalent(dlg, 2, 'S');
  2599.         SetItemKeyEquivalent(dlg, 4, 'D');
  2600.         SysBeep(0);
  2601.         MyModalDialog(dlg, gDialogFilterUPP, &item);
  2602.         err = DoClose(dlg);
  2603.         if (err != noErr) return err;
  2604.         
  2605.         switch (item) {
  2606.             case 1: /* send */
  2607.                 err = SendMessage(wind);
  2608.                 if (err != noErr) return err;
  2609.                 break;
  2610.             case 2: /* save */
  2611.                 err = DoSave(wind, 0);
  2612.                 if (err != noErr) return err;
  2613.                 break;
  2614.             case 3: /* cancel */
  2615.                 return userCanceledErr;
  2616.             case 4: /* discard */
  2617.                 break;
  2618.         }
  2619.     }
  2620.     
  2621.     err = SaveWindPosToFile(wind);
  2622.     if (err != noErr) return err;
  2623.     
  2624.     if ((**info).theTE != nil) TEDispose((**info).theTE);
  2625.     if ((**info).tabField != nil) TEDispose((**info).tabField);
  2626.     if ((**info).quoteStringField != nil) TEDispose((**info).quoteStringField);
  2627.     if ((**info).newsgroupsField != nil) TEDispose((**info).newsgroupsField);
  2628.     if ((**info).toField != nil) TEDispose((**info).toField);
  2629.     if ((**info).subjectField != nil) TEDispose((**info).subjectField);
  2630.     if ((**info).ccField != nil) TEDispose((**info).ccField);
  2631.     if ((**info).bccField != nil) TEDispose((**info).bccField);
  2632.     if ((**info).replytoField != nil) TEDispose((**info).replytoField);
  2633.     if ((**info).followuptoField != nil) TEDispose((**info).followuptoField);
  2634.     if ((**info).keywordsField != nil) TEDispose((**info).keywordsField);
  2635.     if ((**info).distributionField != nil) TEDispose((**info).distributionField);
  2636.     if ((**info).extraNewsField != nil) TEDispose((**info).extraNewsField);
  2637.     if ((**info).extraMailField != nil) TEDispose((**info).extraMailField);
  2638.     if ((**info).signatureField != nil) TEDispose((**info).signatureField);
  2639.     MyDisposeHandle((**info).references);
  2640.     MyDisposeHandle((**info).from);
  2641.     MyDisposeHandle((**info).fields);
  2642.     MyDisposeHandle((**info).alias);
  2643.     MyDisposeHandle(info);
  2644.     
  2645.     if (gHaveDragMgr) {
  2646.         RemoveTrackingHandler(gHandleTrackingUPP, wind);
  2647.         RemoveReceiveHandler(gHandleReceiveUPP, wind);
  2648.     }
  2649.  
  2650.     MyDisposeWindow(wind);
  2651.     return noErr;
  2652. }
  2653.  
  2654.  
  2655.  
  2656. /*----------------------------------------------------------------------------
  2657.     Idle 
  2658.     
  2659.     Handle idle time tasks for a message window.
  2660.             
  2661.     Entry:    wind = pointer to message window.
  2662.     
  2663.     Exit:    cursorRgn = cursor region for WaitNextEvent mouse moved events.
  2664. ----------------------------------------------------------------------------*/
  2665.  
  2666. static void Idle (WindowPtr wind, RgnHandle cursorRgn)
  2667. {
  2668.     TWindow **info;
  2669.     short curField;
  2670.     TEHandle edit, tempEdit;
  2671.     TMsgFieldInfo **fields, *f;
  2672.     short numFields, i;
  2673.     Rect r;
  2674.     static RgnHandle rgn = nil;
  2675.     Point where;
  2676.     unsigned long fileEnabled, editEnabled, specialEnabled;
  2677.     char state;
  2678.     short selStart, selEnd;
  2679.  
  2680.     info = (TWindow**)GetWRefCon(wind);
  2681.     fields = (**info).fields;
  2682.     curField = (**info).curField;
  2683.     numFields = (**info).numFields;
  2684.     edit = (*fields)[curField].edit;
  2685.     TEIdle(edit);
  2686.     
  2687.     if (rgn == nil) rgn = NewRgn();
  2688.     SetEmptyRgn(cursorRgn);
  2689.     state = MyHGetState(fields);
  2690.     MyHLock(fields);
  2691.     for (i = 0, f = *fields; i < numFields; i++, f++) {
  2692.         tempEdit = f->edit;
  2693.         r = (**tempEdit).viewRect;
  2694.         InsetRect(&r, -kTextMargin, 0);
  2695.         LocalToGlobalRect(&r);
  2696.         RectRgn(rgn, &r);
  2697.         UnionRgn(rgn, cursorRgn, cursorRgn);    
  2698.     }
  2699.     MyHSetState(fields, state);
  2700.     if (gHaveDragMgr) SubtractTEHiliteRgn(cursorRgn, edit);
  2701.     
  2702.     GetMouse(&where);
  2703.     LocalToGlobal(&where);
  2704.     
  2705.     if (PtInRgn(where, cursorRgn)) {
  2706.         SetCursor(&gIBeamCurs);
  2707.     } else {
  2708.         SetCursor(&qd.arrow);
  2709.         ComplementRgn(cursorRgn);
  2710.     }
  2711.     
  2712.     fileEnabled = kMessageFileEnabled;
  2713.     if ((**info).alias != nil && !(**info).changed)
  2714.         fileEnabled &= ~kSaveMask;
  2715.     editEnabled = kMessageEditEnabled;
  2716.     selStart = (**edit).selStart;
  2717.     selEnd = (**edit).selEnd;
  2718.     if (selStart >= selEnd) {
  2719.         editEnabled &= ~(kCutMask | kCopyMask | kClearMask | 
  2720.             kEnterSelectionMask | kRot13Mask | kWrapMask | kUnwrapMask);
  2721.     } else if (selEnd > selStart + 255) {
  2722.         editEnabled &= ~kEnterSelectionMask;
  2723.     }
  2724.     if (MyTEGetScrapLen() == 0)
  2725.         editEnabled &= ~(kPasteMask | kPasteAsQuotationMask);
  2726.     if (edit != (**info).theTE)
  2727.         editEnabled &= ~(kPasteAsQuotationMask | kInsertSpoilerCharacterMask | 
  2728.             kRot13Mask | kWrapMask | kUnwrapMask);
  2729.     if ((**edit).teLength == 0) 
  2730.         editEnabled &= ~kSelectAllMask;
  2731.     specialEnabled = kMessageSpecialEnabled;
  2732.     if (!IsURL(edit)) specialEnabled &= ~kOpenURLMask;
  2733.     if (*gFindPattern == 0) editEnabled &= ~kFindAgainMask;
  2734.     SetMenusTo(kAppleAllEnabled, fileEnabled, editEnabled, 
  2735.         kMessageNewsEnabled, specialEnabled, kMessageWindEnabled);
  2736.     SetEditMenuShowHideDetails(!(**info).showDetails);
  2737. }
  2738.  
  2739.  
  2740.  
  2741. /*----------------------------------------------------------------------------
  2742.     InitMessageDispatchTable 
  2743.     
  2744.     Initialize the dispatch table for message windows.
  2745. ----------------------------------------------------------------------------*/
  2746.  
  2747. void InitMessageDispatchTable (void)
  2748. {
  2749.     TDispatch *d;
  2750.     
  2751.     d = &gDispatch[kMessage];
  2752.     
  2753.     d->activate = Activate;
  2754.     d->update = Update;
  2755.     d->mouse = Mouse;
  2756.     d->draggable = Draggable;
  2757.     d->key = Key;
  2758.     d->grow = Grow;
  2759.     d->zoom = Zoom;
  2760.     d->command = Command;
  2761.     d->close = Close;
  2762.     d->idle = Idle;
  2763.     
  2764.     gAutoScrollUPP = NewTEClickLoopProc(AutoScroll);
  2765.     gHandleTrackingUPP = NewDragTrackingHandlerProc(HandleTracking);
  2766.     gHandleReceiveUPP = NewDragReceiveHandlerProc(HandleReceive);
  2767.     gScrollActionUPP = NewControlActionProc(ScrollAction);
  2768. }
  2769.